Евгений Колесников 1 éve
commit
ffa5e413cf
100 módosított fájl, 26419 hozzáadás és 0 törlés
  1. 7 0
      .gitignore
  2. 62 0
      articles/0_tasks.md
  3. 717 0
      articles/4_prog_string.md
  4. 644 0
      articles/cs_misc_types.md
  5. 61 0
      articles/exam.md
  6. 148 0
      articles/ide_idea.md
  7. 554 0
      articles/lab-skv.md
  8. 31 0
      articles/lab-templates.md
  9. 149 0
      articles/lab1.md
  10. 84 0
      articles/lab2.md
  11. 21 0
      articles/lab3.md
  12. 18 0
      articles/lab4.md
  13. 80 0
      articles/lab4_4.md
  14. 43 0
      articles/lab4_5.md
  15. 3 0
      articles/lab5_async.md
  16. 28 0
      articles/lab5_file_types.md
  17. 5 0
      articles/lab5_files.md
  18. 52 0
      articles/lab5_function.md
  19. 36 0
      articles/lab5_regex.md
  20. 5 0
      articles/lab5_try_null.md
  21. 44 0
      articles/lab8-oop-json.md
  22. 121 0
      articles/lab8-oop.md
  23. 183 0
      articles/lab8-oop1.md
  24. 52 0
      articles/lab8-oop2.md
  25. 55 0
      articles/lab8-oop3.md
  26. 76 0
      articles/lab8-oop4.md
  27. 4 0
      articles/lab_class_lib.md
  28. 62 0
      articles/lab_debug.md
  29. 67 0
      articles/lab_truth_table.md
  30. 22 0
      articles/lab_wpf_base.md
  31. 3 0
      articles/lab_wpf_binding.md
  32. 99 0
      articles/lab_wpf_data_csv.md
  33. 133 0
      articles/lab_wpf_data_json.md
  34. 3 0
      articles/lab_wpf_elements.md
  35. 3 0
      articles/lab_wpf_filter.md
  36. 3 0
      articles/lab_wpf_search_sort.md
  37. 174 0
      articles/network_json.md
  38. 314 0
      articles/regex.md
  39. 604 0
      articles/skv.md
  40. 121 0
      articles/skv_lab.md
  41. 43 0
      articles/skv_lab_example.md
  42. 448 0
      articles/t1l1.md
  43. 424 0
      articles/t1l2.md
  44. 352 0
      articles/t1l3.md
  45. 3 0
      articles/t1lab1.md
  46. 170 0
      articles/t2l1.md
  47. 462 0
      articles/t2l2.md
  48. 1012 0
      articles/t3l1.md
  49. 599 0
      articles/t3l1_2.md
  50. 711 0
      articles/t3l1_3.md
  51. 701 0
      articles/t5_delegate.md
  52. 1067 0
      articles/t5_exception.md
  53. 1225 0
      articles/t5_file_types.md
  54. 1393 0
      articles/t5_files.md
  55. 904 0
      articles/t5_function.md
  56. 379 0
      articles/t5_regex.md
  57. 808 0
      articles/t5_thread_async.md
  58. 454 0
      articles/t6_linq.md
  59. 2210 0
      articles/t6_oop1.md
  60. 687 0
      articles/t6_oop2.md
  61. 359 0
      articles/t6_oop_habr.md
  62. 1603 0
      articles/t6_templates.md
  63. 336 0
      articles/t7_dll.md
  64. 699 0
      articles/t8_binding.md
  65. 1259 0
      articles/t8_elements.md
  66. 875 0
      articles/t8_win_app.md
  67. 180 0
      articles/tickets.md
  68. 296 0
      articles/wpf_filtering.md
  69. 212 0
      articles/wpf_listbox.md
  70. 454 0
      articles/wpf_resource.md
  71. 114 0
      articles/wpf_search_sort.md
  72. 621 0
      articles/wpf_style.md
  73. 201 0
      articles/wpf_template.md
  74. 72 0
      articles/wpf_window.md
  75. 17 0
      articles/задачки.md
  76. 102 0
      articles/практика.md
  77. BIN
      doc/[draft] РП Основы АиП.docx
  78. BIN
      doc/s1.docx
  79. 76 0
      doc/task.md
  80. BIN
      doc/КТП Основы Алгоритмизации и Программирования.docx
  81. BIN
      doc/Логические основы алгоритмизации.pptx
  82. BIN
      doc/ОП-04 (Основы алгоритмизации).docx
  83. BIN
      doc/РП Основы Алгоритмизации и Программирования.docx
  84. BIN
      img/01071.png
  85. BIN
      img/01072.png
  86. BIN
      img/01073.png
  87. BIN
      img/03001.png
  88. BIN
      img/03002.png
  89. BIN
      img/03003.png
  90. BIN
      img/03004.png
  91. BIN
      img/03005.png
  92. BIN
      img/03006.png
  93. BIN
      img/03007.png
  94. BIN
      img/03008.png
  95. BIN
      img/03009.png
  96. BIN
      img/03010.png
  97. BIN
      img/03011.png
  98. BIN
      img/03012.png
  99. BIN
      img/03013.png
  100. BIN
      img/03014.png

+ 7 - 0
.gitignore

@@ -0,0 +1,7 @@
+test/*
+practic/*
+.vscode/*
+TODO
+tasks/test.py
+~*
+local

+ 62 - 0
articles/0_tasks.md

@@ -0,0 +1,62 @@
+# Индивидуальные задания для лабораторных работ
+
+## Задания для лабораторной 4.1 (линейные алгоритмы)
+
+* [Просто сложить числа (1)](https://acmp.ru/index.asp?main=task&id_task=1)
+* [Разница между максимумом и минимумом (21)](https://acmp.ru/index.asp?main=task&id_task=21)
+* [33. Вычитание](https://acmp.ru/index.asp?main=task&id_task=33)
+* [21. Эния](https://acmp.ru/index.asp?main=task&id_task=195)
+<!-- перемножить все числа -->
+<!-- (сумма чисел - 1) = всего банок -->
+* [Журавлики](https://acmp.ru/index.asp?main=task&id_task=92)
+<!-- число разделить на 6 -->
+* [Внеземные гости](https://acmp.ru/index.asp?main=task&id_task=597)
+
+    В этой задаче условие пока не делаем - выводим числа
+    <!-- r1 - (r2+r3) -->
+
+* [Бисер](https://acmp.ru/index.asp?main=task&id_task=903)
+<!-- число + 1 -->
+* [Гулливер](https://acmp.ru/index.asp?main=task&id_task=773)
+<!-- x^2 * y -->
+* [Сбор земляники](https://acmp.ru/index.asp?main=task&id_task=755)
+
+    В этой задаче условие пока не делаем - выводим отрицательные числа
+    <!-- x+y-z -->
+
+<!-- string((n//10) * (1 + n//10))+'25' -->
+* [Прямоугольный параллелепипед](https://acmp.ru/index.asp?main=task&id_task=819)
+* [Пятью пять - двадцать пять!](https://acmp.ru/index.asp?main=task&id_task=3)
+
+* [Длина отрезка](https://acmp.ru/index.asp?main=task&id_task=529)
+
+## Задания для лабораторной 4.3 (массивы)
+
+1. [Статистика (5)](https://acmp.ru/index.asp?main=task&id_task=5)
+2. [Домашнее задание (9)](https://acmp.ru/index.asp?main=task&id_task=9)
+3. [Поле чудес (17)](https://acmp.ru/index.asp?main=task&id_task=17)
+4. [Пилообразная последовательность (20)](https://acmp.ru/index.asp?main=task&id_task=20)
+5. [Годовой баланс (32)](https://acmp.ru/index.asp?main=task&id_task=32)
+6. [Секретное сообщение (34)](https://acmp.ru/index.asp?main=task&id_task=34)
+7. [Jивой Jурнал (56)](https://acmp.ru/index.asp?main=task&id_task=56)
+8. [Простой ряд (64)](https://acmp.ru/index.asp?main=task&id_task=64)
+9. [Расстояние Хэмминга (65)](https://acmp.ru/index.asp?main=task&id_task=65)
+10. [Анаграмма (72)](https://acmp.ru/index.asp?main=task&id_task=72)
+11. [Расшифровка (73)](https://acmp.ru/index.asp?main=task&id_task=73)
+12. [Музей (76)](https://acmp.ru/index.asp?main=task&id_task=76)
+13. [Пересечение множеств (82)](https://acmp.ru/index.asp?main=task&id_task=82)
+14. [Строки (87)](https://acmp.ru/index.asp?main=task&id_task=87)
+15. [Быстрый поезд (89)](https://acmp.ru/index.asp?main=task&id_task=89)
+16. [Сортировка времени (119)](https://acmp.ru/index.asp?main=task&id_task=119)
+17. [Свадьба (174)](https://acmp.ru/index.asp?main=task&id_task=174)
+18. [Рабочее время (184)](https://acmp.ru/index.asp?main=task&id_task=184)
+19. [Наибольшее произведение (224)](https://acmp.ru/index.asp?main=task&id_task=224)
+20. [Билетики (244)](https://acmp.ru/index.asp?main=task&id_task=244)
+21. [Вагоны (246)](https://acmp.ru/index.asp?main=task&id_task=246)
+22. [Сортировка масс (252)](https://acmp.ru/index.asp?main=task&id_task=252)
+23. [Кассы (266)](https://acmp.ru/index.asp?main=task&id_task=266)
+24. [Ежеминутные автобусы (313)](https://acmp.ru/index.asp?main=task&id_task=313)
+25. [Лексикографический порядок чисел (314)](https://acmp.ru/index.asp?main=task&id_task=314)
+26. [Преобразование последовательности (326)](https://acmp.ru/index.asp?main=task&id_task=326)
+27. [Покер (347)](https://acmp.ru/index.asp?main=task&id_task=347)
+28. [Закраска прямой (377)](https://acmp.ru/index.asp?main=task&id_task=377)

+ 717 - 0
articles/4_prog_string.md

@@ -0,0 +1,717 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Массивы.](./t3l1_3.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Объявление множества. Работа с датами. Кортежи.](./cs_misc_types.md)
+
+
+# Строки. Объявление строковых типов данных. Поиск, удаление, замена и добавление символов в строке. Операции со строками. Стандартные функции и процедуры работы со строками.
+
+## Строки
+
+С точки зрения регулярного программирования строковый тип данных **string** относится к числу самых важных в **C#**. Этот тип определяет и поддерживает символьные строки. В целом ряде других языков программирования строка представляет собой массив символов. А в **C#** строки являются объектами. Следовательно, тип **string** относится к числу ссылочных.
+
+### Построение строк
+
+Самый простой способ построить символьную строку — воспользоваться строковым литералом. Например, в следующей строке кода переменной ссылки на строку *stroka* присваивается ссылка на строковый литерал:
+
+```cs
+string stroka = "Пример строки";
+```
+
+В данном случае переменная *stroka* инициализируется последовательностью символов `"Пример строки"`. Объект типа **string** можно также создать из массива типа **char**. Например:
+
+```cs
+char[] charArray = {'e', 'x', 'a', 'm', 'p', 'l', 'e'};
+string stroka = new string(charArray);
+```
+
+Как только объект типа **string** будет создан, его можно использовать везде, где только требуется строка текста.
+
+### Постоянство строк
+
+Как ни странно, содержимое объекта типа **string** не подлежит изменению. Это означает, что однажды созданную последовательность символов изменить нельзя. Но данное ограничение способствует более эффективной реализации символьных строк. Поэтому этот, на первый взгляд, очевидный недостаток на самом деле превращается в преимущество. Так, если требуется строка в качестве разновидности уже имеющейся строки, то для этой цели следует создать новую строку, содержащую все необходимые изменения. А поскольку неиспользуемые строковые объекты автоматически собираются в "мусор", то о дальнейшей судьбе ненужных строк можно даже не беспокоиться.
+
+Следует, однако, подчеркнуть, что переменные ссылки на строки (т.е. объекты типа **string**) подлежат изменению, а следовательно, они могут ссылаться на другой объект. Но содержимое самого объекта типа **string** не меняется после его создания.
+
+### Работа со строками
+
+В классе **System.String** предоставляется набор методов для определения длины символьных данных, поиска подстроки в текущей строке, преобразования символов из верхнего регистра в нижний и наоборот, и т.д. Далее мы рассмотрим этот класс более подробно.
+
+### Поле, индексатор и свойство класса **String**
+
+В классе **String** определено единственное поле:
+
+```cs
+public static readonly string Empty;
+```
+
+Поле **Empty** обозначает пустую строку, т.е. такую строку, которая не содержит символы. Этим оно отличается от пустой ссылки типа **String**, которая просто делается на несуществующий объект.
+
+Помимо этого, в классе **String** определен единственный индексатор, доступный только для чтения:
+
+```cs
+public char this[int index] { get; }
+```
+
+Этот индексатор позволяет получить символ по указанному индексу. Индексация строк, как и массивов, начинается с нуля. Объекты типа **String** отличаются постоянством и не изменяются, поэтому вполне логично, что в классе **String** поддерживается индексатор, доступный только для чтения.
+
+И наконец, в классе **String** определено единственное свойство, доступное только для чтения:
+
+```cs
+public int Length { get; }
+```
+
+Свойство *Length* возвращает количество символов в строке. В примере ниже показано использование индексатора и свойства *Length*:
+
+```cs
+using System;
+
+string stroka = "Простая строка";
+
+// Получить длину строки и 6й символ в строке используя индексатор
+Console.WriteLine(
+    "Длина строки - {0}, 6й символ - '{1}'",
+    stroka.Length, 
+    stroka[5]);
+```
+
+### Операторы класса **String**
+
+В классе **String** перегружаются два оператора: `==` и `!=`. Оператор `==` служит для проверки двух символьных строк на равенство. Когда оператор `==` применяется к ссылкам на объекты, он обычно проверяет, ссылаются ли они на один и тот же объект. А когда оператор `==` применяется к ссылкам на объекты типа **String**, то на предмет равенства сравнивается содержимое самих строк. Это же относится и к оператору `!=`. Когда он применяется к ссылкам на объекты типа **String**, то на предмет неравенства сравнивается содержимое самих строк.
+
+Как станет ясно дальше, во многих видах сравнения символьных строк используются сведения о культурной среде. Но это не относится к операторам `==` и `!=`. Ведь они просто сравнивают порядковые значения символов в строках. (Иными словами, они сравнивают двоичные значения символов, не видоизмененные нормами культурной среды, т.е. региональными стандартами.) Следовательно, эти операторы выполняют сравнение строк без учета регистра и настроек культурной среды.
+
+### Методы класса **String**
+
+#### *Compare()*
+
+Сравнение строк
+
+* `public static int Compare(string strA, string strB)`
+
+    Статический метод, сравнивает строку *strA* со строкой *strB*. Возвращает положительное значение, если строка *strA* больше строки *strB*; отрицательное значение, если строка *strA* меньше строки *strB*; и нуль, если строки *strA* и *strB* равны. Сравнение выполняется с учетом регистра и культурной среды.
+
+    ```cs
+    String.Compare(str1, str2)
+    ```
+
+* `public static int Compare(string strA, string strB, bool ignoreCase)`
+
+    Если параметр *ignoreCase* принимает логическое значение **true**, то при сравнении не учитываются различия между прописным и строчным вариантами букв. В противном случае эти различия учитываются.
+
+* `public static int Compare(string strA, string strB, StringComparison comparisonType)`
+
+    Параметр *comparisonType* определяет конкретный способ сравнения строк (перечисление: *CurrentCulture, CurrentCultureIgnoreCase, InvariantCulture, InvariantCultureIgnoreCase, Ordinal, OrdinalIgnoreCase*). 
+
+* `public static int Compare(string strA, string strB, bool ignoreCase, CultureInfo culture)`
+
+    Класс CultureInfo определен в пространстве имен System.Globalization.
+
+* `public static int Compare(string strA, int indexA, string strB, int indexB, int length)`
+
+    Сравнивает части строк strA и strB. Сравнение начинается со строковых элементов strA[indexA] и strB[indexB] и включает количество символов, определяемых параметром *length*. Метод возвращает положительное значение, если часть строки strA больше части строки strB; отрицательное значение, если часть строки strA меньше части строки strB; и нуль, если сравниваемые части строк strA и strB равны. Сравнение выполняется с учетом регистра и культурной среды.
+
+* `public static int Compare(string strA, int indexA, string strB, int indexB, int length, bool ignoreCase)`
+
+* `public static int Compare(string strA, int indexA, string strB, int indexB, int length, StringComparison comparisonType)`
+
+* `public static int Compare(string strA, int indexA, string strB, int indexB, int length, bool ignoreCase, CultureInfo culture)`
+
+
+#### *CompareOrdinal()*
+
+Делает то же, что и метод *Compare()*, но без учета локальных установок
+
+* `public static int CompareOrdinal(string strA, string strB)`
+
+* `public static int CompareOrdinal(string strA, int indexA, string strB, int indexB, int count)`
+
+
+#### *CompareTo()*	
+
+* `public int CompareTo(object value)`
+
+    Сравнивает вызывающую строку со строковым представлением объекта value. Возвращает положительное значение, если вызывающая строка больше строки value; отрицательное значение, если вызывающая строка меньше строки value; и нуль, если сравниваемые строки равны
+
+    ```cs
+    var someObject = new Person();
+    if (someStr.CompareTo(someObject) == 0) {
+        ...
+    }
+    ```
+
+* `public int CompareTo(string strB)`
+
+    Сравнивает вызывающую строку со строкой strB
+
+    ```cs
+    if (someStr.CompareTo("I love C#") == 0) {
+        ...
+    }
+    ```
+
+#### *Equals()*
+
+* `public override bool Equals(object obj)`
+
+    Возвращает логическое значение true, если вызывающая строка содержит ту же последовательность символов, что и строковое представление объекта obj. Выполняется порядковое сравнение с учетом регистра, но без учета культурной среды
+
+* `public bool Equals(string value)`
+
+    Проверка на равно со строкой
+
+    ```cs
+    var otherString = "other string"
+    someString.Equals(otherString)
+    ```
+
+* `public bool Equals(string value, StringComparison comparisonType)`
+
+    Возвращает логическое значение true, если вызывающая строка содержит ту же последовательность символов, что и строка value. Выполняется порядковое сравнение с учетом регистра, но без учета культурной среды. Параметр comparisonType определяет конкретный способ сравнения строк
+
+* `public static bool Equals(string a, string b)`
+
+* `public static bool Equals(string a, string b, StringComparison comparisonType)`
+
+    Возвращает логическое значение true, если строка a содержит ту же последовательность символов, что и строка b. Выполняется порядковое сравнение с учетом регистра, но без учета культурной среды. Параметр comparisonType определяет конкретный способ сравнения строк
+
+#### *Concat()*	
+
+Конкатенация (соединение) строк
+
+* `public static string Concat(string str0, string str1)`
+* `public static string Concat(params string[] values)`
+
+
+#### *Contains()*
+
+* `public bool Contains(string value)`
+
+	Метод, который позволяет определить, содержится ли в строке определенная подстрока (value)
+
+#### *StartsWith()*	
+
+Возвращает логическое значение true, если вызывающая строка начинается с подстроки value. В противном случае возвращается логическое значение false. Параметр comparisonType определяет конкретный способ выполнения поиска
+
+* `public bool StartsWith(string value)`
+* `public bool StartsWith(string value, StringComparison comparisonType)`
+
+
+#### *EndsWith()*
+
+Возвращает логическое значение true, если вызывающая строка оканчивается подстрокой value. В противном случае возвращает логическое значение false. Параметр comparisonType определяет конкретный способ поиска
+
+* `public bool EndsWith(string value)`
+* `public bool EndsWith(string value, StringComparison comparisonType)`	
+
+#### *IndexOf()*
+
+Находит позицию **первого** вхождения заданной подстроки или символа в строке. Если искомый символ или подстрока не обнаружены, то возвращается значение -1
+
+* `public int IndexOf(char value)`
+* `public int IndexOf(string value)`	
+
+Возвращает индекс первого вхождения символа или подстроки value в вызывающей строке. Поиск начинается с элемента, указываемого по индексу startIndex, и охватывает число элементов, определяемых параметром count (если указан). Метод возвращает значение -1, если искомый символ или подстрока не обнаружен
+
+* `public int IndexOf(char value, int startIndex)`
+* `public int IndexOf(string value, int startIndex)`
+* `public int IndexOf(char value, int startIndex, int count)`
+* `public int IndexOf(string value, int startIndex, int count)`	
+
+#### *LastIndexOf()*
+
+Перегруженные версии аналогичны методу IndexOf()	
+
+То же, что *IndexOf*, но находит последнее вхождение символа или подстроки, а не первое
+
+#### *IndexOfAny()*
+
+Возвращает индекс первого вхождения любого символа из массива anyOf, обнаруженного в вызывающей строке. Поиск начинается с элемента, указываемого по индексу startIndex, и охватывает число элементов, определяемых параметром count (если они указаны). Метод возвращает значение -1, если не обнаружено совпадение ни с одним из символов из массива anyOf. Поиск осуществляется порядковым способом
+
+* `public int IndexOfAny(char[] anyOf)`
+* `public int IndexOfAny(char[] anyOf, int startIndex)`
+* `public int IndexOfAny(char[] anyOf, int startIndex, int count)`
+
+#### *LastIndexOfAny()*
+
+Перегруженные версии аналогичны методу *IndexOfAny()*
+
+Возвращает индекс последнего вхождения любого символа из массива anyOf, обнаруженного в вызывающей строке
+
+#### Разделение и соединение строк
+
+#### *Split()*
+
+Метод, возвращающий массив string с присутствующими в данном экземпляре подстроками внутри, которые отделяются друг от друга элементами из указанного массива char или string.
+
+* `public string[] Split(params char[] separator)`
+* `public string[] Split(params char[] separator, int count)`
+
+В первой форме метода *Split()* вызывающая строка разделяется на составные части. В итоге возвращается массив, содержащий подстроки, полученные из вызывающей строки. Символы, ограничивающие эти подстроки, передаются в массиве separator. Если массив separator пуст или ссылается на пустую строку, то в качестве разделителя подстрок используется пробел. А во второй форме данного метода возвращается количество подстрок, определяемых параметром count.
+
+* `public string[] Split(params char[] separator, StringSplitOptions options)`
+
+* `public string[] Split(string[] separator, StringSplitOptions options)`
+
+* `public string[] Split(params char[] separator, int count, StringSplitOptions options)`
+
+* `public string[] Split(string[] separator, int count, StringSplitOptions options)`
+
+В двух первых формах метода Split() вызывающая строка разделяется на части и возвращается массив, содержащий подстроки, полученные из вызывающей строки. Символы, разделяющие эти подстроки, передаются в массиве separator. Если массив separator пуст, то в качестве разделителя используется пробел. А в третьей и четвертой формах данного метода возвращается количество строк, ограничиваемое параметром count.
+
+Но во всех формах параметр options обозначает конкретный способ обработки пустых строк, которые образуются в том случае, если два разделителя оказываются рядом. В перечислении StringSplitOptions определяются только два значения: None и RemoveEmptyEntries. Если параметр options принимает значение None, то пустые строки включаются в конечный результат разделения исходной строки. А если параметр options принимает значение RemoveEmptyEntries, то пустые строки исключаются из конечного результата разделения исходной строки.
+
+#### *Join()*
+
+Строит новую строку, комбинируя содержимое массива строк.
+
+* `public static string Join(string separator, string[] value)`
+* `public static string Join(string separator, string[] value, int startIndex, int count)`
+
+В первой форме метода *Join()* возвращается строка, состоящая из сцепляемых подстрок, передаваемых в массиве value. Во второй форме также возвращается строка, состоящая из подстрок, передаваемых в масс*иве value, но они сцепляются в определенном количестве count, начиная с элемента массива value[startIndex]. В обеих формах каждая последующая строка отделяется от предыдущей разделительной строкой, определяемой параметром separator.
+
+#### Заполнение и обрезка строк
+
+#### *Trim()*
+
+Метод, который позволяет удалять все вхождения определенного набора символов с начала и конца текущей строки.
+
+* `public string Trim()`
+* `public string Trim(params char[] trimChars)`
+
+В первой форме метода *Trim()* из вызывающей строки удаляются начальные и конечные пробелы. А во второй форме этого метода удаляются начальные и конечные вхождения в вызывающей строке символов из массива trimChars. В обеих формах возвращается получающаяся в итоге строка.
+
+#### *PadLeft()*
+
+Позволяет дополнить строку символами слева.
+
+* `public string PadLeft(int totalWidth)`
+* `public string PadLeft(int totalWidth, char paddingChar)`
+
+В первой форме метода PadLeft() вводятся пробелы с левой стороны вызывающей строки, чтобы ее общая длина стала равной значению параметра totalWidth. А во второй форме данного метода символы, обозначаемые параметром paddingChar, вводятся с левой стороны вызывающей строки, чтобы ее общая длина стала равной значению параметра totalWidth. В обеих формах возвращается получающаяся в итоге строка. Если значение параметра totalWidth меньше длины вызывающей строки, то возвращается копия неизмененной вызывающей строки.
+
+#### *PadRight()*
+
+Аналогично PadLeft()	
+
+Позволяет дополнить строку символами справа.
+
+#### Вставка, удаление и замена строк
+
+#### *Insert()*
+
+* `public string Insert(int startIndex, string value)`
+
+Используется для вставки одной строки в другую, где value обозначает строку, вставляемую в вызывающую строку по индексу startIndex. Метод возвращает получившуюся в итоге строку.
+
+#### *Remove()*
+
+* `public string Remove(int startIndex)`
+* `public string Remove(int startIndex, int count)`
+
+Используется для удаления части строки. В первой форме метода *Remove()* удаление выполняется, начиная с места, указываемого по индексу startIndex, и продолжается до конца строки. А во второй форме данного метода из строки удаляется количество символов, определяемое параметром count, начиная с места, указываемого по индексу startIndex.
+
+#### *Replace()*	
+
+* `public string Replace(char oldChar, char newChar)`
+* `public string Replace(string oldValue, string newValue)`
+
+Используется для замены части строки. В первой форме метода Replace() все вхождения символа oldChar в вызывающей строке заменяются символом newChar. А во второй форме данного метода все вхождения строки oldValue в вызывающей строке заменяются строкой newValue.
+
+#### Смена регистра
+
+#### *ToUpper()*
+
+* `public string ToUpper()`
+
+Делает заглавными все буквы в вызывающей строке.
+
+#### *ToLower()*
+
+* `public string ToLower()`
+
+Делает строчными все буквы в вызывающей строке.
+
+#### Получение подстроки из строки
+
+#### *Substring()*
+
+* `public string Substring(int startIndex)`
+* `public string Substring(int startIndex, int length)`
+
+В первой форме метода Substring() подстрока извлекается, начиная с места, обозначаемого параметром startIndex, и до конца вызывающей строки. А во второй форме данного метода извлекается подстрока, состоящая из количества символов, определяемых параметром length, начиная с места, обозначаемого параметром startIndex.
+
+Пример следующей программы использует несколько из вышеуказанных методов:
+
+```cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+// Сравним первые две строки
+string s1 = "это строка";
+string s2 = "это текст, а это строка";
+
+if (String.CompareOrdinal(s1, s2) != 0)
+    Console.WriteLine(
+        "Строки s1 и s2 не равны");
+
+if (String.Compare(s1, 0, s2, 13, 10, true) == 0)
+    Console.WriteLine(
+        "При этом в них есть одинаковый текст");
+
+// Конкатенация строк
+Console.WriteLine(
+    String.Concat(
+        "\n" + "Один, два ",
+        "три, четыре"));
+
+// Поиск в строке
+
+// Первое вхождение подстроки
+if (s2.IndexOf("это") != -1)
+    Console.WriteLine(
+        "Слово \"это\" найдено в строке, оно "+ 
+        "находится на: {0} позиции", 
+        s2.IndexOf("это"));
+
+// Последнее вхождение подстроки
+if (s2.LastIndexOf("это") != -1)
+    Console.WriteLine(
+        "Последнее вхождение слова \"это\" находится "
+        + "на {0} позиции", 
+        s2.LastIndexOf("это"));
+
+// Поиск из массива символов
+char[] myCh = {'Ы','х','т'};
+if (s2.IndexOfAny(myCh) != -1)
+    Console.WriteLine(
+        "Один из символов из массива ch "+
+        "найден в текущей строке на позиции {0}", 
+        s2.IndexOfAny(myCh));
+
+// Определяем начинается ли строка с заданной подстроки
+if (s2.StartsWith("это текст") == true)
+    Console.WriteLine("Подстрока найдена!");
+
+// Определяем содержится ли в строке подстрока
+// на примере определения ОС пользователя
+string myOS = Environment.OSVersion.ToString();
+if (myOS.Contains("NT 5.1"))
+    Console.WriteLine("Ваша операционная система Windows XP");
+else if (myOS.Contains("NT 6.1"))
+Console.WriteLine("Ваша операционная система Windows 7");
+
+Console.ReadLine();
+```
+
+![](../img/04008.png)
+
+### Немного о сравнении строк в C#
+
+Вероятно, из всех операций обработки символьных строк чаще всего выполняется сравнение одной строки с другой. Прежде чем рассматривать какие-либо методы сравнения строк, следует подчеркнуть следующее: сравнение строк может быть выполнено в среде **.NET** двумя основными способами:
+
+**Во-первых**, сравнение может отражать обычаи и нормы отдельной культурной среды, которые зачастую представляют собой настройки культурной среды, вступающие в силу при выполнении программы. Это стандартное поведение некоторых, хотя и не всех методов сравнения.
+
+И **во-вторых**, сравнение может быть выполнено независимо от настроек культурной среды только по порядковым значениям символов, составляющих строку. Вообще говоря, при сравнении строк без учета культурной среды используется лексикографический порядок (и лингвистические особенности), чтобы определить, является ли одна строка больше, меньше или равной другой строке. При порядковом сравнении строки просто упорядочиваются на основании невидоизмененного значения каждого символа.
+
+Выбор способа сравнения символьных строк представляет собой весьма ответственное решение. Как правило и без всяких исключений, следует выбирать сравнение строк с учетом культурной среды, если это делается для целей отображения результата пользователю (например, для вывода на экран ряда строк, отсортированных в лексикографическом порядке). Но если строки содержат фиксированную информацию, не предназначенную для видоизменения с учетом отличий в культурных средах, например, имя файла, ключевое слово, адрес веб-сайта или значение, связанное с обеспечением безопасности, то следует выбрать порядковое сравнение строк. Разумеется, особенности конкретного разрабатываемого приложения будут диктовать выбор подходящего способа сравнения символьных строк.
+
+В классе **String** предоставляются самые разные методы сравнения строк, которые перечислены выше. Наиболее универсальным среди них является метод *Compare()*. Он позволяет сравнивать две строки полностью или частично, с учетом или без учета регистра, способа сравнения, определяемого параметром типа StringComparison, а также сведений о культурной среде, предоставляемых с помощью параметра типа CultureInfo.
+
+Те перегружаемые варианты метода *Compare()*, которые не содержат параметр типа StringComparison, выполняют сравнение символьных строк с учетом регистра и культурной среды. А в тех перегружаемых его вариантах, которые не содержат параметр типа CultureInfo, сведения о культурной среде определяются текущей средой выполнения.
+
+Тип StringComparison представляет собой перечисление, в котором определяются значения, приведенные в таблице ниже. Используя эти значения, можно организовать сравнение строк, удовлетворяющее потребностям конкретного приложения. Следовательно, добавление параметра типа StringComparison расширяет возможности метода Compare() и других методов сравнения, например, Equals(). Это дает также возможность однозначно указывать способ предполагаемого сравнения строк.
+
+В силу имеющих отличий между сравнением строк с учетом культурной среды и порядковым сравнением очень важно быть предельно точным в этом отношении.
+
+Значения, определяемые в перечислении StringComparison
+
+Значение | Описание
+---------|--------
+CurrentCulture | Сравнение строк производится с использованием текущих настроек параметров культурной среды
+CurrentCultureIgnoreCase | Сравнение строк производится с использованием текущих настроек параметров культурной среды, но без учета регистра
+InvariantCulture | Сравнение строк производится с использованием неизменяемых, т.е. универсальных данных о культурной среде
+InvariantCultureIgnoreCase | Сравнение строк производится с использованием неизменяемых, т.е. универсальных данных о культурной среде и без учета регистра
+Ordinal | Сравнение строк производится с использованием порядковых значений символов в строке. При этом лексикографический порядок может нарушиться, а условные обозначения, принятые в отдельной культурной среде, игнорируются
+OrdinalIgnoreCase | Сравнение строк производится с использованием порядковых значений символов в строке, но без учета регистра
+
+
+В любом случае метод *Compare()* возвращает отрицательное значение, если первая сравниваемая строка оказывается меньше второй; положительное значение, если первая сравниваемая строка больше второй; и наконец, нуль, если обе сравниваемые строки равны. Несмотря на то что метод *Compare()* возвращает нуль, если сравниваемые строки равны, для определения равенства символьных строк, как правило, лучше пользоваться методом *Equals()* или же оператором `==`.
+
+Дело в том, что метод *Compare()* определяет равенство сравниваемых строк на основании порядка их сортировки. Так, если выполняется сравнение строк с учетом культурной среды, то обе строки могут оказаться одинаковыми по порядку их сортировки, но не равными по существу. По умолчанию равенство строк определяется в методе *Equals()*, исходя из порядковых значений символов и без учета культурной среды. Следовательно, по умолчанию обе строки сравниваются в этом методе на абсолютное, посимвольное равенство подобно тому, как это делается в операторе `==`.
+
+Несмотря на большую универсальность метода *Compare()*, для простого порядкового сравнения символьных строк проще пользоваться методом CompareOrdinal(). И наконец, следует иметь в виду, что метод CompareTo() выполняет сравнение строк только с учетом культурной среды.
+
+В приведенной ниже программе демонстрируется применение методов *Compare()*, *Equals()*, *CompareOrdinal()*, а также операторов `==` и `!=` для сравнения символьных строк. Обратите внимание на то, что два первых примера сравнения наглядно демонстрируют отличия между сравнением строк с учетом культурной среды и порядковым сравнением в англоязычной среде:
+
+```cs
+using System;
+
+string str1 = "alpha";
+string str2 = "Alpha";
+string str3 = "Beta";
+string str4 = "alpha";
+string str5 = "alpha, beta";
+int result;
+
+// Сначала продемонстрировать отличия между сравнением строк
+// с учетом культурной среды и порядковым сравнением
+result = String.Compare(str1, str2, StringComparison.CurrentCulture);
+Console.Write("Сравнение строк с учетом культурной среды: ");
+
+if (result < 0)
+    Console.WriteLine(str1 + " меньше " + str2);
+else if(result > 0)
+    Console.WriteLine(str1 + " больше " + str2);
+else
+    Console.WriteLine(str1 + " равно " + str2);
+
+result = String.Compare(str1, str2, StringComparison.Ordinal);
+Console.Write("Порядковое сравнение строк: ");
+if (result < 0)
+    Console.WriteLine(str1 + " меньше " + str2);
+else if(result > 0)
+    Console.WriteLine(str1 + " больше " + str2);
+else 
+    Console.WriteLine(str1 + " равно " + str4);
+
+// Использовать метод CompareOrdinal()
+result = String.CompareOrdinal(str1, str2);
+Console.Write("Сравнение строк методом CompareOrdinal():\n");
+if (result < 0)
+    Console.WriteLine(str1 + " меньше " + str2);
+else if(result > 0)
+    Console.WriteLine(str1 + " больше " + str2);
+else
+    Console.WriteLine(str1 + " равно " + str4);
+Console.WriteLine();
+
+// Определить равенство строк с помощью оператора ==
+// Это порядковое сравнение символьных строк
+if (str1 == str4) 
+    Console.WriteLine(str1 + " == " + str4);
+
+// Определить неравенство строк с помощью оператора !=
+if(str1 != str3) 
+    Console.WriteLine(str1 + " != " + str3);
+
+if(str1 != str2) 
+    Console.WriteLine(str1 + " != " + str2);
+Console.WriteLine();
+
+// Выполнить порядковое сравнение строк без учета регистра,
+// используя метод Equals()
+if(String.Equals(str1, str2, StringComparison.OrdinalIgnoreCase))
+    Console.WriteLine(
+        "Сравнение строк методом Equals() с " + 
+        "параметром OrdinalIgnoreCase:\n" + 
+        str1 + " равно " + str2);
+Console.WriteLine ();
+
+// Сравнить части строк
+if(String.Compare(str2, 0, str5, 0, 3,
+    StringComparison.CurrentCulture) > 0) {
+    Console.WriteLine(
+        "Сравнение строк с учетом текущей культурной среды:" + 
+        "\n3 первых символа строки " + str2 + 
+        " больше, чем 3 первых символа строки " + str5);
+```
+
+Выполнение этой программы приводит к следующему результату:
+
+![](../img/04009.png)
+
+## Класс **StringBuilder**
+
+Когда строка конструируется классом **String**, выделяется ровно столько памяти, сколько необходимо для хранения данной строки. Однако, в пространстве имен **System.Text** имеется класс **StringBuilder**, который поступает лучше и обычно выделяет больше памяти, чем нужно в данный момент. У вас, как разработчика, есть возможность указать, сколько именно памяти должен выделить StringBuilder, но если вы этого не сделаете, то будет выбран объем по умолчанию, который зависит от размера начального текста, инициализирующего экземпляр **StringBuilder**. Класс **StringBuilder** имеет два главных свойства:
+
+**Length**, показывающее длину строки, содержащуюся в объекте в данный момент
+
+**Capacity**, указывающее максимальную длину строки, которая может поместиться в выделенную для объекта память
+
+Любые модификации строки происходят внутри блока памяти, выделенного экземпляру **StringBuilder**. Это делает добавление подстрок и замену индивидуальных символов строки очень эффективными. Удаление или вставка подстрок неизбежно остаются менее эффективными, потому что при этих операциях приходится перемещать в памяти части строки. Выделять новую память и, возможно, полностью перемещать ее содержимое приходится только при выполнении ряда действий, которые приводят к превышению выделенной емкости строки. В дополнение к избыточной памяти, выделяемой изначально на основе экспериментов, **StringBuilder** имеет свойство удваивать свою емкость, когда происходит переполнение, а новое значение емкости не установлено явно.
+
+Рассмотрим пример:
+
+```cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+StringBuilder hello = new StringBuilder(
+    "Привет, меня зовут Александр Ерохин",120);
+
+hello.AppendFormat(
+    "Я рад вас приветствовать на моем сайте professorweb.ru");
+```
+
+В данном примере начальная емкость StringBuilder равна 120. Всегда лучше сразу указывать емкость, превышающую предполагаемую длину строки, чтобы объекту StringBuilder не приходилось заново выделять память при переполнении. По умолчанию устанавливается емкость в 16 символов. Схематически данный пример работает следующим образом:
+
+![](../img/04010.png)
+
+Т.е. при вызове метода AppendFormat() остаток текста помещается в пустое пространство без необходимости перераспределения памяти. Однако реальная эффективность, которую несет с собой применение StringBuilder, проявляется при выполнении повторных подстановок текста.
+
+В следующей таблице перечислены основные методы класса StringBuilder:
+
+Метод | Назначение
+------|-----------
+Append() | Добавляет строку к текущей строке
+AppendFormat() | Добавляет строку, сформированную в соответствии со спецификатором формата
+Insert() | Вставляет подстроку в строку
+Remove() | Удаляет символ из текущей строки
+Replace() | Заменяет все вхождения символа другим символом или вхождения подстроки другой подстрокой
+ToString() | Возвращает текущую строку в виде объекта System.String (переопределение метода класса System.Object)
+
+
+Давайте рассмотрим практический пример использования класса StringBuilder:
+
+```cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+StringBuilder hello = new StringBuilder(
+    "Привет, меня зовут Александр Ерохин",120);
+
+hello.AppendFormat(
+    " Я рад вас приветствовать на моем сайте professorweb.ru");
+
+// Зашифруем строку, хранящуюся в переменной hello
+Console.WriteLine(
+    "Исходная строка: \n {0}\n",
+    hello);
+
+for (int i = 'я'; i >= 'a'; i--)
+    hello = hello.Replace((char)i,(char)(i+3));
+
+Console.WriteLine(
+    "Зашифрованная строка:\n {0}",
+    hello);
+
+Console.ReadLine();
+```
+
+![](../img/04011.png)
+
+## Форматирующие строки
+
+Если вы хотите, чтобы разрабатываемые классы были дружественными к пользователю, они должны предлагать средства для отображения своих строковых представлений в любом из форматов, которые могут понадобиться пользователю. В исполняющей среде .NET определен стандартный способ достижения этого — интерфейс **IFormattable**. 
+
+Начнем с рассмотрения того, что происходит, когда форматная строка применяется к примитивному типу, а отсюда станет ясно, как следует включать спецификаторы формата для пользовательских классов и структур:
+
+```cs
+decimal d = 12.05667m;
+int i = 5;
+
+Console.WriteLine(
+    "Значение переменной d = {0:C}, а i = {1}",
+    d, i);
+```
+
+Сама строка формата содержит большую часть отображаемого текста, но всякий раз, когда в нее должно быть вставлено значение переменной, в фигурных скобках указывается индекс. В фигурные скобки может быть включена и другая информация, относящаяся к формату данного элемента, например, та, что описана ниже:
+
+* Количество символов, которое займет представление элемента, снабженное префиксом-запятой. Отрицательное число указывает, что элемент должен быть выровнен по левой границе, а положительное — по правой. Если элемент на самом деле занимает больше символов, чем ему отведено форматом, он отображается полностью.
+
+* Спецификатор формата предваряется двоеточием. Это указывает, каким образом необходимо отформатировать элемент. Например, можно указать, должно ли число быть форматировано как денежное значение, либо его следует отобразить в научной нотации.
+
+В следующей таблице перечислены часто используемые спецификаторы формата для числовых типов:
+
+Спецификатор | Применяется к | Значение | Пример
+:-----------:|---------------|----------|----
+C | Числовым типам | Символ местной валюты | $835.50 (США) £835.50 (Великобритания) 835.50р.(Россия)
+D | Только к целочисленным типам | Обычное целое | 835
+E | Числовым типам | Экспоненциальная нотация | 8.35Е+002
+F | Числовым типам | С фиксированной десятичной точкой | 835.50
+G | Числовым типам | Обычные числа | 835.5
+N | Числовым типам | Формат чисел, принятый в данной местности	4,384.50 (Великобритания/США), 4 384,50 (континентальная Европа)
+P | Числовым типам | Процентная нотация | 835,000.00%
+X | Только к целочисленным типам | Шестнадцатеричный формат | 1a1f
+
+Стоит отметить, что полный список спецификаторов формата значительно длинее, поскольку другие типы данных добавляют собственные спецификаторы.
+
+### Форматирование даты и времени
+
+Помимо числовых значений, форматированию нередко подлежит и другой тип данных: DateTime. Это структура, представляющая дату и время. Значения даты и времени могут отображаться самыми разными способами.
+
+Форматирование даты и времени осуществляется с помощью спецификаторов формата. Конкретное представление даты и времени может отличаться в силу региональных и языковых особенностей и поэтому зависит от настройки параметров культурной среды. Спецификаторы формата даты и времени сведены в следующей таблице:
+
+Спецификатор | Формат
+:-----------:|------
+D | Дата в длинной форме
+d | Дата в краткой форме
+F | Дата и время в длинной форме
+f | Дата и время в краткой форме
+G | Дата — в краткой форме, время — в длинной
+g | Дата и время — в краткой форме
+М | Месяц и день
+m | То же, что и М
+O | Формат даты и времени, включая часовой пояс. Строка, составленная в формате О, может быть преобразована обратно в эквивалентную форму вывода даты и времени. Это так называемый "круговой" формат
+R | Дата и время в стандартной форме по Гринвичу
+s | Сортируемый формат представления даты и времени
+T | Время в длинной форме
+t | Время в краткой форме
+U | Длинная форма универсального представления даты и времени; время отображается как универсальное синхронизированное время (UTC)
+u | Краткая форма универсального представления даты и времени
+Y | Месяц и год
+
+В приведенном ниже примере программы демонстрируется применение спецификаторов формата даты и времени:
+
+```cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+DateTime myDate = DateTime.Now;
+
+Console.WriteLine(
+    "Дата в формате d: {0:d}\nВ формате D: {0:D}",
+    myDate);
+
+Console.WriteLine(
+    "Дата в формате f: {0:f}\nВ формате F: {0:F}", 
+    myDate);
+
+Console.WriteLine(
+    "Дата в формате g: {0:g}\nВ формате G: {0:G}", 
+    myDate);
+
+Console.WriteLine(
+    "Дата в формате m: {0:m}\nВ формате M: {0:M}", 
+    myDate);
+
+Console.WriteLine(
+    "Дата в формате r: {0:r}\nВ формате R: {0:R}", 
+    myDate);
+
+Console.WriteLine(
+    "Дата в формате o: {0:o}\nВ формате O: {0:O}", 
+    myDate);
+
+Console.WriteLine(
+    "Дата в формате s: {0:s}", 
+    myDate);
+
+Console.WriteLine(
+    "Дата в формате t: {0:t}\nВ формате T: {0:T}", 
+    myDate);
+
+Console.WriteLine(
+    "Дата в формате u: {0:u}\nВ формате U: {0:U}", 
+    myDate);
+
+Console.WriteLine(
+    "Дата в формате y: {0:y}\nВ формате Y: {0:Y}", 
+    myDate);
+
+Console.ReadLine();
+```
+
+![](../img/04012.png)
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Массивы.](./t3l1_3.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Объявление множества. Работа с датами. Кортежи.](./cs_misc_types.md)

+ 644 - 0
articles/cs_misc_types.md

@@ -0,0 +1,644 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Строки.](./4_prog_string.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Общие сведения о подпрограммах.](./t5_function.md)
+
+# Перечисления. Множества. Работа с датами. Кортежи.
+
+* [Перечисления](#перечисления-enum)
+* [Кортежи](#кортежи-tuple)
+* [Множества](#множества)
+* [Работа с датами и временем](#работа-с-датами-и-временем)
+
+## Перечисления (enum)
+
+Кроме примитивных типов данных в **C#** есть такой тип как **enum** или **перечисление**. Перечисления представляют набор логически связанных констант. Объявление перечисления происходит с помощью оператора **enum**. Далее идет название перечисления, после которого указывается тип перечисления - он обязательно должен представлять целочисленный тип (**byte**, **int**, **short**, **long**). Если тип явным образом не указан, то **по умолчанию используется тип int**. Затем идет список элементов перечисления через запятую:
+
+```cs
+enum Days
+{
+    Monday,
+    Tuesday,
+    Wednesday,
+    Thursday,
+    Friday,
+    Saturday,
+    Sunday
+}
+```
+
+```cs
+enum Time : byte
+{
+    Morning,
+    Afternoon,
+    Evening,
+    Night
+}
+```
+
+В примерах выше каждому элементу перечисления присваивается целочисленное значение, причем первый элемент будет иметь значение `0`, второй `1` и так далее. Мы можем также явным образом указать значения элементов, либо указав значение первого элемента:
+
+```cs
+enum Operation
+{ 
+    Add = 1,   // каждый следующий элемент по умолчанию увеличивается на единицу
+    Subtract, // этот элемент равен 2
+    Multiply, // равен 3
+    Divide    // равен 4
+}
+```
+
+Но можно и для всех элементов явным образом указать значения:
+
+```cs
+enum Operation
+{ 
+    Add = 2,
+    Subtract = 4,
+    Multiply = 8,
+    Divide = 16
+}
+```
+
+При этом константы перечисления могут иметь одинаковые значения, либо даже можно присваивать одной константе значение другой константы:
+
+```cs
+enum Color
+{
+    White = 1,
+    Black = 2,
+    Green = 2,
+    Blue = White // Blue = 1
+}
+```
+
+Каждое перечисление фактически определяет новый тип данных. Затем в программе мы можем определить переменную этого типа и использовать ее:
+
+```cs
+enum Operation
+{
+    Add = 1,
+    Subtract,
+    Multiply,
+    Divide
+}
+
+Operation op;
+op = Operation.Add;
+Console.WriteLine(op); // Add
+    
+Console.ReadLine();
+```
+
+В программе мы можем присвоить значение этой переменной. При этом в качестве её значения должна выступать одна из констант, определенных в данном перечислении. То есть несмотря на то, что каждая константа сопоставляется с определенным числом, мы не можем присвоить ей числовое значение, например, `Operation op = 1;`. И также если мы будем выводить на консоль значение этой переменной, то мы получим имя константы, а не числовое значение. Если же необходимо получить числовое значение, то следует выполнить приведение к числовому типу:
+
+```cs
+Operation op;
+op = Operation.Multiply;
+Console.WriteLine((int) op); // 3
+                  ^^^^^ 
+```
+
+Зачастую переменная перечисления выступает в качестве хранилища состояния, в зависимости от которого производятся некоторые действия. Так, рассмотрим применение перечисления на более реальном примере:
+
+```cs
+enum Operation
+{
+    Add = 1,
+    Subtract,
+    Multiply,
+    Divide
+}
+
+// Тип операции задаем с помощью константы Operation.Add
+MathOp(10, 5, Operation.Add);
+
+// Тип операции задаем с помощью константы Operation.Multiply
+MathOp(11, 5, Operation.Multiply);
+        
+Console.ReadLine();
+
+static void MathOp(double x, double y, Operation op)
+{
+    double result = 0.0;
+
+    switch (op)
+    {
+        case Operation.Add:
+            result = x + y;
+            break;
+        case Operation.Subtract:
+            result = x - y;
+            break;
+        case Operation.Multiply:
+            result = x * y;
+            break;
+        case Operation.Divide:
+            result = x / y;
+            break;
+    }
+
+    Console.WriteLine("Результат операции равен {0}", result);
+}
+```
+
+---
+
+## Кортежи (Tuple)
+
+Кортежи предоставляют удобный способ для работы с набором значений.
+
+Кортеж представляет набор значений, заключенных в круглые скобки:
+
+```cs
+var tuple = (5, 10);
+```
+
+В данном случае определен кортеж *tuple*, который имеет два значения: `5` и `10`. В дальнейшем мы можем обращаться к каждому из этих значений через поля с названиями `Item+<порядковый_номер_поля_в_кортеже>`. Например:
+
+```cs
+var tuple = (5, 10);
+Console.WriteLine(tuple.Item1); // 5
+Console.WriteLine(tuple.Item2); // 10
+tuple.Item1 += 26;
+Console.WriteLine(tuple.Item1); // 31
+Console.Read();
+```
+
+В данном случае тип определяется неявно. Но мы также можем явным образом указать для переменной кортежа тип:
+
+```cs
+(int, int) tuple = (5, 10);
+```
+
+Так как кортеж содержит два числа, то в определении типа нам надо указать два числовых типа. Или другой пример определения кортежа:
+
+```cs
+(string, int, double) person = ("Tom", 25, 81.23);
+```
+
+Первый элемент кортежа в данном случае представляет строку, второй элемент - тип **int**, а третий - тип **double**.
+
+Мы также можем дать названия полям кортежа:
+
+```cs
+var tuple = (count:5, sum:10);
+             ^^^^^    ^^^
+Console.WriteLine(tuple.count); // 5
+Console.WriteLine(tuple.sum); // 10
+```
+
+Теперь чтобы обратиться к полям кортежа используются их имена, а не названия _Item1_ и _Item2_.
+
+Мы даже можем не использовать переменную для определения всего кортежа, а использовать отдельные переменные для его полей:
+
+```cs
+var (name, age) = ("Tom", 23);
+Console.WriteLine(name);    // Tom
+Console.WriteLine(age);     // 23
+Console.Read();
+```
+
+### Использование кортежей
+
+Кортежи могут передаваться в качестве параметров в метод, могут быть возвращаемым результатом функции, либо использоваться иным образом.
+
+Например, одной из распространенных ситуаций является возвращение из функции двух и более значений, в то время как функция можно возвращать только одно значение. И кортежи представляют оптимальный способ для решения этой задачи:
+
+```cs
+var tuple = GetValues();
+Console.WriteLine(tuple.Item1); // 1
+Console.WriteLine(tuple.Item2); // 3
+    
+Console.Read();
+
+private static (int, int) GetValues()
+{
+    var result = (1, 3);
+    return result;
+}
+```
+
+Здесь определен метод _GetValues_, который возвращает кортеж. Кортеж определяется как набор значений, помещенных в круглые скобки. И в данном случае мы возвращаем кортеж из двух элементов типа **int**, то есть два числа.
+
+Другой пример:
+
+```cs
+var tuple = GetNamedValues(new int[]{ 1,2,3,4,5,6,7});
+Console.WriteLine(tuple.count);
+Console.WriteLine(tuple.sum);
+    
+Console.Read();
+
+private static (int sum, int count) GetNamedValues(int[] numbers)
+{
+    var result = (sum:0, count: 0);
+    for (int i=0; i < numbers.Length; i++)
+    {
+        result.sum += numbers[i];
+        result.count++;
+    }
+    return result;
+}
+```
+
+И также кортеж может передаваться в качестве параметра в метод:
+
+```cs
+var (name, age) = GetTuple(("Tom", 23), 12);
+Console.WriteLine(name);    // Tom
+Console.WriteLine(age);     // 35
+Console.Read();
+         
+private static (string name, int age) GetTuple((string n, int a) tuple, int x)
+{
+    var result = (name: tuple.n, age: tuple.a + x);
+    return result;
+}
+```
+
+## Множества
+
+Коллекция, содержащая только уникальные элементы, называется множеством (**set**). В составе **.NET** имеются два множества — **HashSet`<T>`** и **SortedSet`<T>`**. Оба они реализуют интерфейс **ISet`<T>`**. Класс **HashSet`<T>`** содержит неупорядоченный список различающихся элементов, а в **SortedSet`<T>`** элементы упорядочены.
+
+Интерфейс **ISet`<T>`** предоставляет методы для **создания**, **объединения** нескольких множеств, **пересечения** множеств и определения, является ли одно множество надмножеством или подмножеством другого.
+
+Ниже перечислены наиболее употребительные конструкторы, определенные в классе **HashSet`<T>`**:
+
+* `public HashSet ()`
+
+    создается пустое множество
+
+* `public HashSet(IEnumerable<T> collection)`
+
+    создается множество, состоящее из элементов указываемой коллекции _collection_
+
+* `public HashSet(IEqualityCompare comparer)`
+
+    допускается указывать способ сравнения с помощью параметра comparer
+
+* `public HashSet(IEnumerable<T> collection, IEqualityCompare comparer)`
+
+    создается множество, состоящее из элементов указываемой коллекции collection, и используется заданный способ сравнения comparer.
+    
+Имеется также пятая форма конструктора данного класса, в которой допускается инициализировать множество последовательно упорядоченными данными.
+
+В этом классе предоставляется также метод _RemoveWhere_, удаляющий из множества элементы, удовлетворяющие заданному условию, или предикату. Помимо свойств, определенных в интерфейсах, которые реализуются в классе **HashSet`<T>`**, в него введено дополнительное свойство _Comparer_, приведенное ниже:
+
+```cs
+public IEqualityComparer<T> Comparer { get; }
+```
+
+Оно позволяет получать метод сравнения для вызывающего хеш-множества.
+
+Ниже перечислены четыре наиболее часто используемых конструкторов, определенных в классе SortedSet<T>:
+
+* `public SortedSet()`
+
+    создается пустое множество
+
+* `public SortedSet(IEnumerable<T> collection)`
+
+    создается множество, состоящее из элементов указываемой коллекции collection
+
+* `public SortedSet(IComparer comparer)`
+
+    допускается указывать способ сравнения с помощью параметра comparer
+
+* `public SortedSet(IEnumerable<T> collection, IComparer comparer)`
+
+    создается множество, состоящее из элементов указываемой коллекции collection, и используется заданный способ сравнения comparer
+
+Имеется также пятая форма конструктора данного класса, в которой допускается инициализировать множество последовательно упорядоченными данными.
+
+В этом классе предоставляется также метод _GetViewBetween_, возвращающий часть множества в форме объекта типа **SortedSet`<T>`**, метод _RemoveWhere_, удаляющий из множества элементы, не удовлетворяющие заданному условию, или предикату, а также метод _Reverse_, возвращающий объект типа **IEnumerable`<T>`**, который циклически проходит множество в обратном порядке.
+
+Помимо свойств, определенных в интерфейсах, которые реализуются в классе **SortedSet`<T>`**, в него введены дополнительные свойства, приведенные ниже:
+
+* `public IComparer<T> Comparer { get; }`
+* `public T Max { get; }`
+* `public T Min { get; }`
+
+Свойство _Comparer_ получает способ сравнения для вызывающего множества. Свойство _Мах_ получает наибольшее значение во множестве, а свойство _Min_ — наименьшее значение во множестве. Давайте рассмотрим пример использования множеств:
+
+```cs
+// Создадим два множества
+SortedSet<char> ss = new SortedSet<char>();
+SortedSet<char> ss1 = new SortedSet<char>();
+
+ss.Add('A');
+ss.Add('B');
+ss.Add('C');
+ss.Add('Z');
+ShowColl(ss, "Первая коллекция: ");
+
+ss1.Add('X');
+ss1.Add('Y');
+ss1.Add('Z');
+ShowColl(ss1, "Вторая коллекция");
+
+ss.SymmetricExceptWith(ss1);
+ShowColl(ss,"Исключили разноименность (одинаковые элементы) двух множеств: ");
+
+ss.UnionWith(ss1);
+ShowColl(ss, "Объединение множеств: ");
+
+ss.ExceptWith(ss1);
+ShowColl(ss, "Вычитание множеств");
+
+Console.ReadLine();
+
+static void ShowColl(SortedSet<char> ss, string s)
+{
+    Console.WriteLine(s);
+    foreach (char ch in ss)
+        Console.Write(ch + " ");
+    Console.WriteLine("\n");
+}
+```
+
+Самописанный пример, объединяющий работу перечислений и множеств:
+
+```cs
+enum Days
+{
+    Monday,
+    Tuesday,
+    Wednesday,
+    Thursday,
+    Friday,
+    Saturday,
+    Sunday
+}
+
+HashSet<Days> DaysHashSet = new HashSet<Days>();
+DaysHashSet.Add(Days.Friday);
+DaysHashSet.Add(Days.Friday);
+DaysHashSet.Add(Days.Monday);
+
+foreach (Days DayItem in DaysHashSet)
+    Console.WriteLine(DayItem);
+
+Console.Write("Press ENTER to continue...");
+Console.ReadLine();
+```
+
+Программа выведет только `Friday` и `Monday`. Т.е. дубли в множество не добавляются и при этом никаких ошибок не возникает. Такое поведение может быть полезно при отслеживании состояния какого-то устройства. Периодически опрашивая устройство мы можем устанавливать состояния не проверя установлено уже это состояние или нет.
+
+Для коллекций вообще и для множеств в частности есть методы объединения, пересечения и разности.
+
+### Разность последовательностей
+
+С помощью метода **Except** можно получить разность двух последовательностей:
+
+```cs
+var DaysHashSet1 = new HashSet<Days>() { Days.Friday, Days.Monday };
+var DaysHashSet2 = new HashSet<Days>() { Days.Friday, Days.Sunday };
+
+var ResHashSet = DaysHashSet1.Except(DaysHashSet2);
+
+foreach (Days DayItem in ResHashSet)
+    Console.WriteLine(DayItem);
+
+Console.Write("Press ENTER to continue...");
+Console.ReadLine();
+```
+
+В данном случае из множества *DaysHashSet1* убираются все элементы, которые есть в массиве *DaysHashSet2*. Результатом операции будет один элемент:
+
+```
+Monday
+```
+
+### Пересечение последовательностей
+
+Для получения пересечения последовательностей, то есть общих для обоих наборов элементов, применяется метод _Intersect_:
+
+```cs
+string[] soft = { "Microsoft", "Google", "Apple"};
+string[] hard = { "Apple", "IBM", "Samsung"};
+ 
+// пересечение последовательностей
+var result = soft.Intersect(hard);
+ 
+foreach (string s in result)
+    Console.WriteLine(s);
+```
+
+Так как оба набора имеют только один общий элемент, то соответственно только он и попадет в результирующую выборку:
+
+```
+Apple
+```
+
+### Объединение последовательностей
+
+Для объединения двух последовательностей используется метод **Union**. Его результатом является новый набор, в котором имеются элементы, как из первой, так и из второй последовательности. Повторяющиеся элементы добавляются в результат только один раз:
+
+```cs
+string[] soft = { "Microsoft", "Google", "Apple"};
+string[] hard = { "Apple", "IBM", "Samsung"};
+ 
+// объединение последовательностей
+var result = soft.Union(hard);
+ 
+foreach (string s in result)
+    Console.WriteLine(s);
+```
+
+Результатом операции будет следующий набор:
+
+```
+Microsoft
+Google
+Apple
+IBM
+Samsung
+```
+
+Если же нам нужно простое объединение двух наборов, то мы можем использовать метод **Concat**:
+
+```cs
+var result = soft.Concat(hard);
+```
+
+Те элементы, которые встречаются в обоих наборах, дублируются.
+
+### Удаление дубликатов
+
+Для удаления дублей в наборе используется метод **Distinct**:
+
+```cs
+var result = soft.Concat(hard).Distinct();
+```
+
+Последовательное применение методов *Concat* и *Distinct* будет подобно действию метода *Union*.
+
+--- 
+
+## Работа с датами и временем
+
+Для работы с датами и временем в **.NET** предназначена структура **DateTime**. Она представляет дату и время от `00:00:00 1 января 0001 года` до `23:59:59 31 декабря 9999` года.
+
+Для создания нового объекта **DateTime** также можно использовать конструктор. Пустой конструктор создает начальную дату:
+
+```cs
+DateTime date1 = new DateTime();
+Console.WriteLine(date1); // 01.01.0001 0:00:00
+```
+
+То есть мы получим минимально возможное значение, которое также можно получить следующим образом:
+
+```cs
+Console.WriteLine(DateTime.MinValue);
+```
+
+Чтобы задать конкретную дату, нужно использовать один из конструкторов, принимающих параметры:
+
+```cs
+DateTime date1 = new DateTime(2015, 7, 20); // год - месяц - день
+Console.WriteLine(date1); // 20.07.2015 0:00:00
+```
+
+Установка времени:
+
+```cs
+DateTime date1 = new DateTime(2015, 7, 20, 18, 30, 25); // год - месяц - день - час - минута - секунда
+Console.WriteLine(date1); // 20.07.2015 18:30:25
+```
+
+Если необходимо получить текущую время и дату, то можно использовать ряд свойств **DateTime**:
+
+```cs
+Console.WriteLine(DateTime.Now);
+Console.WriteLine(DateTime.UtcNow);
+Console.WriteLine(DateTime.Today);
+```
+
+Консольный вывод:
+
+```
+20.07.2015 11:43:33
+20.07.2015 8:43:33
+20.07.2015 0:00:00
+```
+
+Свойство *DateTime.Now* возвращает текущие дату и время компьютера (локальные), *DateTime.UtcNow* - дату и время относительно времени по Гринвичу (GMT) и *DateTime.Today* - только текущую дату (время сброшено в ноль).
+
+При работе с датами надо учитывать, что по умолчанию для представления дат применяется григорианский календарь. Но что будет, если мы захотим получить день недели для `5 октября 1582` года:
+
+```cs
+DateTime someDate = new DateTime(1582, 10, 5);
+Console.WriteLine(someDate.DayOfWeek);
+```
+
+Консоль выстветит значение `Tuesday`, то есть вторник. Однако, как может быть известно из истории, впервые переход с юлианского календаря на григорианский состоялся в октябре 1582 года. Тогда после даты 4 октября (четверг) (еще по юлианскому календарю) сразу перешли к 15 октября (пятница)(уже по григорианскому календарю). Таким образом, фактически выкинули 10 дней. То есть после 4 октября шло 15 октября.
+
+В большинстве случаев данный факт вряд ли как-то повлияет на вычисления, однако при работе с очень давними датами данный аспект следует учитывать.
+
+### Операции с DateTime
+
+Основные операции со структурой **DateTime** связаны со сложением или вычитанием дат. Например, надо к некоторой дате прибавить или, наоборот, отнять несколько дней.
+
+Для добавления дат используется ряд методов:
+
+* `Add(TimeSpan value)`: добавляет к дате значение типа **TimeSpan**
+
+* `AddDays(double value)`: добавляет к текущей дате несколько дней
+
+* `AddHours(double value)`: добавляет к текущей дате несколько часов
+
+* `AddMinutes(double value)`: добавляет к текущей дате несколько минут
+
+* `AddMonths(int value)`: добавляет к текущей дате несколько месяцев
+
+* `AddYears(int value)`: добавляет к текущей дате несколько лет
+
+Например, добавим к некоторой дате 3 часа:
+
+```cs
+DateTime date1 = new DateTime(2015, 7, 20, 18, 30, 25); // 20.07.2015 18:30:25
+Console.WriteLine(date1.AddHours(3)); // 20.07.2015 21:30:25
+```
+
+Для вычитания дат используется метод `Substract(DateTime date)`:
+
+```cs
+DateTime date1 = new DateTime(2015, 7, 20, 18, 30, 25); // 20.07.2015 18:30:25
+DateTime date2 = new DateTime(2015, 7, 20, 15, 30, 25); // 20.07.2015 15:30:25
+Console.WriteLine(date1.Subtract(date2)); // 03:00:00
+```
+
+Здесь даты различаются на три часа, поэтому результатом будет **TimeSpan** "03:00:00".
+
+Метод **Substract** не имеет возможностей для отдельного вычитания дней, часов и так далее. Но это и не надо, так как мы можем передавать в метод _AddDays_ и другие методы добавления отрицательные значения:
+
+```cs
+// вычтем три часа
+DateTime date1 = new DateTime(2015, 7, 20, 18, 30, 25);  // 20.07.2015 18:30:25
+Console.WriteLine(date1.AddHours(-3)); // 20.07.2015 15:30:25
+```
+
+### Форматирование даты
+
+Кроме операций сложения и вычитания еще есть ряд методов форматирования дат:
+
+
+```cs
+DateTime date1 = new DateTime(2015, 7, 20, 18, 30, 25);
+Console.WriteLine(date1.ToLocalTime()); // 20.07.2015 21:30:25
+Console.WriteLine(date1.ToUniversalTime()); // 20.07.2015 15:30:25
+Console.WriteLine(date1.ToLongDateString()); // 20 июля 2015 г.
+Console.WriteLine(date1.ToShortDateString()); // 20.07.2015
+Console.WriteLine(date1.ToLongTimeString()); // 18:30:25
+Console.WriteLine(date1.ToShortTimeString()); // 18:30
+```
+
+Метод _ToLocalTime_ преобразует время **UTC** в локальное время, добавляя смещение относительно времени по Гринвичу. Метод _ToUniversalTime_, наоборот, преобразует локальное время во время **UTC**, то есть вычитает смещение относительно времени по Гринвичу. Остальные методы преобразуют дату к определенному формату.
+
+Метод _ToString_ тоже можно использовать для форматирования. По умолчанию для русской культурной среды он возвращает дату в формате `ДД.ММ.ГГГГ Ч:ММ:СС`. Но можно задать нужный формат в параметрах: 
+`дата.ToString("dd.MM.yyyy HH:mm:ss");`
+
+Описатель | Описание
+:---:|----
+d | Представляет день месяца от 1 до 31. Одноразрядные числа используются без нуля в начале
+dd | Представляет день месяца от 1 до 31. К одноразрядным числам в начале добавляется ноль
+ddd | Сокращенное название дня недели (пн,вт...)
+dddd | Полное название дня недели (понедельник, вторник...)
+f / fffffff | Представляет миллисекунды. Количество символов f указывает на число разрядов в миллисекундах
+g | Представляет период или эру (например, "н. э.")
+h | Часы в виде от 1 до 12. Часы с одной цифрой не дополняются нулем
+hh | Часы в виде от 01 до 12. Часы с одной цифрой дополняются нулем
+H | Часы в виде от 0 до 23. Часы с одной цифрой не дополняются нулем
+HH | Часы в виде от 0 до 23. Часы с одной цифрой дополняются нулем
+K | Часовой пояс
+m | Минуты от 0 до 59. Минуты с одной цифрой не дополняются начальным нулем
+mm | Минуты от 0 до 59. Минуты с одной цифрой дополняются начальным нулем
+M | Месяц в виде от 1 до 12
+MM | Месяц в виде от 1 до 12. Месяц с одной цифрой дополняется начальным нулем
+MMM | Сокращенное название месяца
+MMMM | Полное название месяца
+s | Секунды в виде числа от 0 до 59. Секунды с одной цифрой не дополняются начальным нулем
+ss | Секунды в виде числа от 0 до 59. Секунды с одной цифрой дополняются начальным нулем
+t | Первые символы в обозначениях AM и PM
+tt | AM или PM
+y | Представляет год как число из одной или двух цифр. Если год имеет более двух цифр, то в результате отображаются только две младшие цифры
+yy | Представляет год как число из одной или двух цифр. Если год имеет более двух цифр, то в результате отображаются только две младшие цифры. Если год имеет одну цифру, то он дополняется начальным нулем
+yyy | Год из трех цифр
+yyyy | Год из четырех цифр
+yyyyy | Год из пяти цифр. Если в году меньше пяти цифр, то он дополняется начальными нулями
+z | Представляет смещение в часах относительно времени UTC
+zz | Представляет смещение в часах относительно времени UTC. Если смещение представляет одну цифру, то она дополняется начальным нулем.
+
+---
+
+## Контрольные вопросы
+
+1. [Перечисления](#перечисления-enum)
+1. [Кортежи](#кортежи-tuple)
+1. [Множества](#множества)
+1. [Дата и время](#работа-с-датами-и-временем)
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Строки.](./4_prog_string.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Общие сведения о подпрограммах.](./t5_function.md)

+ 61 - 0
articles/exam.md

@@ -0,0 +1,61 @@
+# Примерное содержание экзамена
+
+>Состав и критерии оценок могут измениться
+
+Написать **РАБОЧЕЕ** (то есть приложение собирается и запускается) оконное приложение, отображающее список данные (**ListBox**), с возможностью фильтрации, поиска и сортировки данных:
+
+Проверяются **только результаты, опубликованные в репозитории**
+
+Предметная область, тип фильтрации и вид списка определяются перед экзаменом генератором случайных чисел
+
+1. Создать модель (класс) по заданной предметной области. **Обязательно** использовать типы: _целое_, _вещественное_, _дата_, _строка_, _логическое_. Одно из строковых полей должно быть словарным - `1` балл <!-- 1 -->
+
+    - несоответствие полей модели предметной области: штраф `0,2` балла
+    - отсутствие типа данных: штраф `0,2` балла
+
+1. Создать набор данных (10 записей) для модели в заданном формате и написать класс поставщика данных **LocalDataProvider**, **CSVDataProvider** или **JSONDataProvider**: до `1.5` баллов <!-- 2.5 -->
+
+    - **программный список** (определен в дата провайдере): `0,5` баллов
+    - **CSV** (загрузка из внешнего файла): `1` балл
+    - **JSON** (загрузка из внешнего файла): `1,5` балла
+
+1. вывести логотип компании - `0,5` балла (доступа в интернет при выполнении задания не будет, в качестве логотипа можно использовать любую загогулину, нарисованную в `paint`) <!-- 3 -->
+
+1. вывести на экран список (**ListBox**) с набором данных из поставщика данных (тип списка **StackPanel** или **WrapPanel** выбирает преподаватель) - `1` балл <!-- 4 -->
+
+1. реализовать поиск по текcтовому полю - `1` балл <!-- 5 -->
+
+1. сделать сортировку по полю - `1` балл <!-- 6 -->
+
+1. сделать фильтрацию по словарному полю или по условию (определяется преподавателем перед началом экзамена) - `1` балл <!-- 7 -->
+
+1. Написать пояснительную записку в **README.MD** (название предметной области, **скриншот**, **набор данных, оформленный как `код`**) - `0,5` балла <!-- 7.5 -->
+
+    Штраф за отсутствующий элемент `0,2` балла
+
+1. Единый стиль переменных во всём приложении (**CamelCase** или **camelCase**): `0.5` балла <!-- 8 -->
+
+1. Самоочевидные названия переменных и названий визуальных объектов: `0.5` балла <!-- 8.5 -->
+
+1. Файловая структура проекта (_для каждого класса свой файл_, _название файла соответствует классу_, _классы модели в отдельном каталоге_): `0.5` балла <!-- 9 -->
+
+    Штраф `0,2` балла за критерий
+
+Итого `9` баллов:
+
+- менее `2`-х баллов, оценка "2"
+- `2 ... 3,9` балла, оценка "3"
+- `4 ... 5,9` баллов, оценка "4"
+- `6` и более баллов, оценка "5"
+
+<!-- 
+
+Зачечания к экзамену:
+
+1. Класс в нескольких файлах (разные неймспейсы)
+1. CamelCase
+
+    public DateOnly? dateclownvisits { get; set; }
+    
+
+-->

+ 148 - 0
articles/ide_idea.md

@@ -0,0 +1,148 @@
+[содержание](/readme.md)  
+
+# Основы работы с IntelliJ IDEA. Интерфейс программы
+
+Для написания Java-программы (и естественно, Kotlin) по большому счету достаточно обыкновенного текстового редактора, но, конечно же, такой вариант просто несопоставим с использованием профессиональных сред разработки приложений, так называемых IDE (Integrated Development Environment).
+
+**IntelliJ IDEA** – это интегрированная среда разработки программного обеспечения на Java/Kotlin от компании *JetBrains*. Кстати, не только на них. Среда с успехом используется и для других языков программирования, например, *Scala*. Первая версия программы появилась в 2001 г. и с тех пор программа неуклонно повышает свой рейтинг популярности. *IntelliJ IDEA* выпускается в двух редакциях: *Community Edition* и *Ultimate Edition*. Первая версия является полностью бесплатной. Вторая версия распространяется под различными лицензиями и, как декларируется, может использоваться бесплатно для разработки проектов с открытым программным кодом.
+
+Версии отличаются также поддерживаемыми технологиями.
+
+1. Ultimate Edition:
+
+* полнофункциональная среда разработки под JVM и разработке на различных языках: Java, Kotlin, PHP, JavaScript, HTML, CSS, SQL, Ruby, Python;
+* поддержка технологий Java EE, Spring/Hibernate и других;
+* внедрение и отладка с большинством серверов приложений.
+
+2. Community Edition:
+
+* полнофункциональная среда разработки для Java SE, Kotlin, Groovy и Scala;
+* мощная среда для разработки под Google Android.
+
+Программа содержит полный набор необходимых для создания полноценных приложений компонент: *редактор*, *среда компиляции и выполнения*, а также *отладчик*.
+
+Естественно, *IntelliJ IDEA* – не единственная среда создания приложений для Java, достаточно припомнить популярную *Eclipse* или *NetBeans*, так что разработчику есть из чего выбирать.
+
+## Установка системы
+
+Скопировать дистрибутив можно с сайта разработчика компании JetBrains по ссылке http://www.jetbrains.com/idea/. Установка *IntelliJ IDEA* проблем не вызывает. Отмечу только, что при инсталляции установите ассоциацию программы (Create associations) с файлами Java и Kotlin.
+
+## Интерфейс программы
+
+### Стартовое окно
+
+После установки при первоначальной загрузке IntelliJ IDEA появляется стартовое окно «Welcome to IntelliJ IDEA», позволяющее загрузить либо открыть проект, импортировать проект, выполнить его загрузку из репозитория нескольких систем контроля версий («Check out from Version Control»). При наличии в проекте файлов настройки сборки для Maven или Gradle, IntelliJ IDEA предложит вам использовать их для конфигурации.
+
+После работы с определенным проектом, он запоминается, и при последующем запуске программы происходит загрузка последнего открытого проекта. Впрочем, это происходит при настройках программы по умолчанию, когда в настройках программы (*File* - *Settings*) в группе *Appearance & Behavior* (Внешний вид и поведение), *System Setting* (Настройки программы) установлен флажок для поля-метки *Reopen last project on startup* (Открывать последний проект при загрузке).
+
+![](/img/ide_01.png)
+
+### Основное окно
+
+Основное окно программы в общем виде состоит из трех областей, называемых также *инструментальными* окнами (на рисунке отмечены красным) и окна *редактора* (оранжевое).
+
+![](/img/ide_02.png)
+
+**Инструментальные** окна располагаются по периметру окна редактора, то есть слева, справа и внизу от него на полях главного окна, которые в дальнейшем будем называть боковыми панелями (sidebar в терминологии программы). Поскольку инструментальные окна отображают разноплановую информацию, то каждая боковая панель содержит ряд вкладок, которые открываются при выполнении определенной команды. Переход к нужной вкладке (инструментальному окну) осуществляется щелчком на ее названии, которые располагаются на боковых панелях главного окна. Названию некоторых вкладок инструментальных окон предваряет цифра. Используя клавишу <Alt> совместно с этой цифрой, можно быстро перейти к этой вкладке, попутно открыв ее, если она находится в свернутом положении, либо, наоборот, свернуть ее.
+
+Перед кратким описанием инструментальных окон оговорюсь, что рассматриваемая структура расположения предлагается такой, какой она является после установки программы по умолчанию. Именно такое расположение я и буду рассматривать далее. Однако это вовсе не означает, что инструментальные окна нельзя расположить в других местах главного окна, о чем речь пойдет ниже.
+
+### Окно редактора
+
+Окно редактора отображается постоянно, занимая большую часть основного окна. Оно может содержать несколько вкладок, отображающих содержимое разных файлов проекта. О содержимом вкладки сигнализирует как расширение файла в названии вкладки, так и пиктограмма перед названием.
+
+Программа содержит внушительный инструментарий управления вкладками окна. Так, расположение вкладок можно произвольно изменять, располагая их, например, горизонтально, перебрасывая файлы из одной группы вкладок в другую, что достигается при помощи группы команд *Windows* - *Editor Tabs* либо из контекстного меню, вызываемого на вкладке окна редактирования. При необходимости конкретную вкладку можно закрепить, что бывает полезным при большом количестве вкладок, когда все они не помещаются в окне редактирования, для чего используем команду *Pin Tab*, о чем речь пойдет ниже.
+
+С правого края окна (в районе полосы прокрутки) могут находятся горизонтальные линии, отмечающие проблемные блоки кода, содержащие ошибки и предупреждения. А в правом вершнем углу отображается общее сосотяние файла (красный - есть ошибки, желтый - есть предупреждения, зеленый - все нормально). Подробнее об этом также позже.
+
+По левому краю окна редактирования расположены метки блоков кода, при помощи которых можно быстро свернуть блок за ненадобностью либо вновь его развернуть. С этой же стороны окна располагаются точки останова (при их наличии), советы по модификации кода и некоторая другая информация.
+
+![](/img/ide_03.png)
+
+Для отображения нумерации строк программного кода следует вызвать контекстное меню (кликнуть правой кнопкой мыши) на вертикальной полосе в левой части окна редактирования и выбрать *Show Lines Numbers* (Отображать нумерацию строк). Однако при таком действии отображение строк осуществляется только в текущем сеансе. Для постоянного же отображения нумерации строк программного кода следует в настройках раскрыть последовательно пункты *Editor* (Редактор), *General* (Общие настройки), *Appearance* (Внешний вид) и установить флажок для поля-метки **Show line numbers** (Отображать номера строк).
+
+Сам программный код (подсветка текста, шрифты) оформляются в соответствии с настройками программы, о чем речь пойдет позже.
+
+## Инструментальные окна
+
+### Инструментальное окно проекта
+
+На левой боковой панели отображается инструментальное окно проекта. Оно содержит вкладку иерархической структуры проекта (Project), вкладку структуры (списка метода) класса (Structure) и вкладку Favorites (Избранное), в которой отображаются закладки и точки останова.
+
+Выбор во вкладке структуры проекта приводит к отображению его содержимого в окне редактора. Поскольку код практически любого класса содержит множество методов/функций, то вкладка «Structure» как раз и отображает их список. Он может быть упорядочен как по алфавиту (Sort by Alphabetically), так и в порядке их расположения в (Sort by Visibility). Щелчок на имени класса инициирует переход на начало метода или функции в окне редактора.
+
+![](/img/ide_04.png)
+
+### Инструментальное окно **Избранное**
+
+В нижней части левой боковой панели основного окна можно вывести инструментальное окно «Favorites» (Избранное), содержащее, например, список точек останова и закладок, обеспечивая тем самым к ним быстрый доступ.
+
+### Инструментальное окно с инструментами сборки проектов
+
+Данное окно располагается на правой боковой панели. Оно изначально содержит вкладки для наиболее распространенных инструментов сборки проектов Java – Gradle (или Maven) и Ant.
+
+### Инструментальное окно вывода
+Окно располагается на нижней инструментальной панели. В нем в зависимости от характера информации отображаются, например, сообщения компиляции («Messages»), консольный ввод/вывод («Terminal»), контроль изменений проекта («Version Control»), результаты работы отладчика («Debug») и некоторые другие.
+
+## Создание проекта
+
+Создается проект просто: *File* - *New* - *Project*. А вот дальше возникают вопросы: какого типа у нас проект, какие библиотеки нужны...
+
+![](/img/ide_05.png)
+
+Мы в своих проектах будем использовать Gradle и Kotlin/JVM. Если со вторым пунктом (язык программирования и реализация под JavaVirualMachine) сомнений нет, то с первым пока непонятно. Я выбрал Gradle, потому что он используется по-умолчанию в Android Studio с которым мы познакомимся в следующем году. Но что вообще означают эти слова: Maven, Gradle?
+
+Если коротко, то это системы сборки проектов, а если подробно, то читайте, например, [тут](https://habr.com/ru/post/107085/)
+
+
+## Структура проекта
+
+При создания нового проекта система создает для нас необходимую структуру каталогов и файлы настроек. Нам из всего этого пока понадобится только каталог с исходными кодами и настройки системы сборки проектов:
+
+![](/img/ide_07.png)
+
+Для создания нового файла нужно кликнуть правой кнопкой мыши на подкаталоге **kotlin** в каталоге **src** и выбрать *Kotlin File/Class*
+
+![](/img/ide_06.gif)
+
+В появившемся окне введите название файла и выберите из списка **File**
+
+![](/img/ide_08.png)
+
+В первом созданном файле (у нас он называется main.kt, но это не обязательно) сделайте точку входа в программу:
+
+```kt
+fun main(args: Array<String>){
+
+}
+```
+
+Любая программа (консольная, про андроид мы пока не говорим) начинается с функции **main**.
+
+В лабораторных работах, чтобы не плодить проекты, мы будем использовать один и тот же проект с одним главным файлом, а для разных задач создавать дополнительные файлы, например:
+
+```kt
+//файл lab1.kt
+class SomeClass(var value: String){
+    fun printValue() = print(value)
+}
+
+fun lab1(){
+    val someClassInstanse = SomeClass("test")
+    someClassInstanse.printValue()
+}
+```
+
+```kt
+//файл main.kt
+fun main(args: Array<String>){
+    lab1()
+}
+```
+
+Все файлы одного пакета (название пакета мы не задавали, но он назначается системой) собираются в одину программу. Таким образом весь код лабораторной работы мы будем писать в отдельном файле (*lab1.kt*), а вызывать его из функции **main**. Это упростит нам работу с репозиторием (с ним мы познакомимся на следующем занатии) и уменьшит количество мусора на диске.
+
+
+[Содрано отсюда](https://www.kv.by/blog/users/fetisovvs/1049285-osnovy-raboty-s-intellij-idea-interfeys-programmy)
+
+[содержание](/readme.md)  

+ 554 - 0
articles/lab-skv.md

@@ -0,0 +1,554 @@
+[содержание](/readme.md)  
+
+TODO: написать про ветвления и слияния
+
+# Системы контроля версий
+
+**Цель работы**: ознакомиться с основами системы контроля версий Git
+
+## Теоретические сведения
+
+### О контроле версий
+
+**Система контроля версий** (СКВ) — это система, регистрирующая изменения в одном или нескольких файлах с тем, чтобы в дальнейшем была возможность вернуться к определённым старым версиям этих файлов. Программисты обычно помещают в систему контроля версий исходные коды программ, но на самом деле под версионный контроль можно поместить файлы практически любого типа.
+
+Если вы графический или веб­дизайнер и хотели бы хранить каждую версию изображения или макета, то пользоваться системой контроля версий будет очень мудрым решением. СКВ даёт возможность возвращать отдельные файлы к прежнему виду, возвращать к прежнему состоянию весь проект, просматривать происходящие со временем изменения, определять, кто последним вносил изменения во внезапно переставший работать модуль, кто и когда внёс в код какую­-то ошибку, и многое другое. Вообще, если, пользуясь СКВ, вы всё испортите или потеряете файлы, всё можно будет легко восстановить. Вдобавок, накладные расходы будут очень маленькими.
+
+### Распределённые системы контроля версий
+
+Про *локальные* и *централизованные* СКВ мы говорили на лекциях, здесь мы рассмотрим только *распределённые* системы контроля версий (РСКВ). В таких системах как *Git*, *Mercurial*, *Bazaar* или *Darcs* клиенты не просто выгружают последние версии файлов, а полностью копируют весь репозиторий (репозиторий ­ место, где хранятся и поддерживаются какие­-либо данные, в данном случае, данные, находящиеся под версионным контролем). Поэтому в случае, когда "умирает" сервер, через который шла работа, любой клиентский репозиторий может быть скопирован обратно на сервер, чтобы восстановить базу данных. Каждый раз, когда клиент забирает свежую версию файлов, он создаёт себе полную копию всех данных.
+
+![](/img/skv_01.png)
+
+Кроме того, в большей части этих систем можно работать с несколькими удалёнными репозиториями, таким образом, можно одновременно работать по-­разному с разными группами людей в рамках одного проекта. Так, в одном проекте можно одновременно вести несколько типов рабочих процессов, что невозможно в централизованных системах.
+
+### Основы Git
+
+#### Почти все операции — локальные
+
+Для совершения большинства операций в Git'е необходимы только локальные файлы и ресурсы, т.е. обычно информация с других компьютеров в сети не нужна. Поскольку вся история проекта хранится локально у вас на диске, большинство операций кажутся практически мгновенными.
+
+К примеру, чтобы показать историю проекта, Git'у не нужно скачивать её с сервера, он просто читает её прямо из вашего локального репозитория. Поэтому историю вы увидите практически мгновенно. Если вам нужно просмотреть изменения между текущей версией файла и версией, сделанной месяц назад, Git может взять файл месячной давности и вычислить разницу на месте, вместо того чтобы запрашивать разницу у СКВ-­сервера или качать с него старую версию файла и делать локальное сравнение.
+
+Кроме того, работа локально означает, что мало чего нельзя сделать без доступа к Сети или VPN. Если вы в самолёте или в поезде и хотите немного поработать, можно спокойно делать коммиты, а затем отправить их, как только станет доступна сеть. Если вы пришли домой, а VPN-­клиент не работает, всё равно можно продолжать работать. Во многих других системах это невозможно или же крайне неудобно. Например, используя Perforce, вы мало что можете сделать без соединения с сервером. Работая с Subversion и CVS, вы можете редактировать файлы, но сохранить изменения в вашу базу данных нельзя (потому что она отключена от репозитория).
+
+#### Git следит за целостностью данных
+
+Перед сохранением любого файла Git вычисляет контрольную сумму, и она становится индексом этого файла. Поэтому невозможно изменить содержимое файла или каталога так, чтобы Git не узнал об этом. Эта функциональность встроена в сам фундамент Git'а и является важной составляющей его философии. Если информация потеряется при передаче или повредится на диске, Git всегда это выявит.
+
+Механизм, используемый Git'ом для вычисления контрольных сумм, называется SHA­1 хешем. Это строка из 40 шестнадцатеричных символов (0­9 и a­f), вычисляемая в Git'е на основе содержимого файла или структуры каталога. SHA­1 хеш выглядит примерно так:
+
+```
+24b9da6552252987aa493b52f8696cd6d3b00373
+```
+
+Работая с Git'ом, вы будете встречать эти хеши повсюду, поскольку он их очень широко использует. Фактически, в своей базе данных Git сохраняет всё не по именам файлов, а по хешам их содержимого.
+
+#### Чаще всего данные в Git только добавляются
+
+Практически все действия, которые вы совершаете в Git'е, только добавляют данные в базу. Очень сложно заставить систему удалить данные или сделать что­то неотменяемое. Можно, как и в любой другой СКВ, потерять данные, которые вы ещё не сохранили, но как только они зафиксированы, их очень сложно потерять, особенно если вы регулярно отправляете изменения в другой репозиторий.
+
+#### Три состояния
+
+Это самое важное, что нужно помнить про Git, если вы хотите, чтобы изучение шло гладко. В Git'е файлы могут находиться в одном из трёх состояний: *зафиксированном*, *изменённом* и *подготовленном*. 
+
+* К **изменённым** относятся файлы, которые поменялись, но ещё не были зафиксированы. 
+* **Подготовленные** файлы — это изменённые файлы, отмеченные для включения в следующий коммит (git add).
+* **Зафиксированный** означает, что файл уже сохранён в вашей локальной базе (git commit). 
+
+Таким образом, в проектах, использующих Git, есть три части: каталог Git'а (Git directory), рабочий каталог (working directory) и область подготовленных файлов (staging area).
+
+![](/img/skv_02.png)
+
+**Каталог Git'а** — это место, где Git хранит метаданные и базу данных объектов вашего проекта. Это наиболее важная часть Git'а, и именно она копируется, когда вы клонируете репозиторий с другого компьютера.
+
+**Рабочий каталог** — это извлечённая из базы копия определённой версии проекта. Эти файлы достаются из сжатой базы данных в каталоге Git'а и помещаются на диск для того, чтобы вы их просматривали и редактировали.
+
+**Область подготовленных файлов** — это обычный файл, обычно хранящийся в каталоге Git'а, который содержит информацию о том, что должно войти в следующий коммит. Иногда его называют индексом (index), но в последнее время становится стандартом называть его областью подготовленных файлов (staging area).
+
+Стандартный рабочий процесс с использованием Git'а выглядит примерно так:
+
+1. Вы вносите изменения в файлы в своём рабочем каталоге.
+2. Подготавливаете файлы, добавляя их слепки в область подготовленных файлов.
+3. Делаете коммит, который берёт подготовленные файлы из индекса и помещает их в каталог Git'а на постоянное хранение.
+
+Если рабочая версия файла совпадает с версией в каталоге Git'а, файл считается зафиксированным. Если файл изменён, но добавлен в область подготовленных данных, он подготовлен. Если же файл изменился после выгрузки из БД, но не был подготовлен, то он считается изменённым.
+
+## Практическая часть
+
+С системой контроля версий можно работать многими способами: далее будет рассмотрена работа с Git через консоль.
+
+### Установите Git
+
+если ее еще нет на вашем компьютере.
+
+>Используйте командную оболочку Git Bash, входящую в состав Git, потому что так вы сможете запускать сложные команды, приведённые в примерах. Командная оболочка Windows использует иной синтаксис, из­-за чего примеры в ней могут работать некорректно.
+
+### Первоначальная настройка Git
+
+Теперь, когда Git установлен в вашей системе, необходимо настроить среду для работы с Git'ом под себя. Это нужно сделать только один раз — при обновлении версии Git'а настройки сохранятся. Но вы можете поменять их в любой момент, выполнив те же команды снова.
+
+В Git'е есть команда ``git config``, которая позволяет просматривать и устанавливать параметры, контролирующие все аспекты работы Git'а и его внешний вид.
+
+#### Имя пользователя
+
+Первое, что вам следует сделать после установки Git'а, — указать ваше имя и адрес электронной почты. Это важно, потому что каждый коммит в Git'е содержит эту информацию, и она включена в коммиты, передаваемые вами, и не может быть далее изменена:
+
+```
+git config ­­--global user.name "John Doe"
+git config ­­--global user.email johndoe@example.com
+```
+
+**Внимание!!!** Глобальные настройки (параметр *global*) нужно делать только на личном компьютере. В колледже этого делать не нужно. Настройки имени и почты нужно делать без параметра *global* в своем репозитории.
+
+#### Проверка настроек
+
+Если вы хотите проверить используемые настройки, можете использовать команду ``git config --­­list``:
+
+```
+git config ­­--list 
+user.name=Scott Chacon 
+user.email=schacon@gmail.com 
+...
+```
+
+### Создание Git-репозитория
+
+Для создания Git-­репозитория существуют два основных подхода. **Первый** подход — импорт в Git уже существующего проекта или каталога. **Второй** — клонирование уже существующего репозитория с сервера.
+
+#### Создание репозитория в существующем проекте
+
+Для начала нужно создать **пустой** git-репозиторий (не ставить галочку у "создать readme")
+
+![](/img/skv_04.png)
+
+При создании пустого репозитория сервер показывает какие команды нужно выполнить для инициализации локального репозитория и отправки его в удаленный репозиторий:
+
+```
+git init
+git add README.md
+git commit -m "first commit"
+git remote add origin https://github.com/kolei/empty.git
+git push -u origin master
+```
+
+Рассмотрим эти команды подробнее:
+
+* **git init** - эта команда создаёт в текущем каталоге новый подкаталог с именем *.git* содержащий все необходимые файлы репозитория. На этом этапе ваш проект ещё не находится под версионным контролем.
+
+* **git add README.md** - добавляет под версионный контроль файл *README.md* (напоминаю, это только пример, вам здесь нужно добавить свои файлы). Чтобы проиндексировать все файлы, можно вместо имени файла указать точку или звездочку **git add .**
+
+* **git commit -m "first commit"** - сохраняет текущее состояние в локальном репозитории.
+
+* **git remote add origin https://github.com/kolei/empty.git** - добавляет в настройки репозитория адрес удаленного git-репозитория. Здесь **origin** - это алиас (короткое имя) для репозитория.
+
+* **git push -u origin master** - отправляет локальный репозиторий на удаленный сервер.
+
+#### Клонирование существующего репозитория
+
+Клонирование существующего репозитория осуществляется командой ``git clone [url]``. Например, если вы хотите клонировать эти лекции, вы можете сделать это следующим образом:
+
+```
+git clone https://github.com/kolei/oap
+```
+
+Эта команда создаёт каталог с именем **oap**, инициализирует в нём каталог .git, скачивает все данные для этого репозитория и создаёт (checks out) рабочую копию последней версии. Если вы зайдёте в новый каталог oap, вы увидите в нём проектные файлы, пригодные для работы и использования. Если вы хотите клонировать репозиторий в каталог, отличный от oap, можно это указать в следующем параметре командной строки:
+
+```
+git clone https://github.com/kolei/oap myoap
+```
+
+Эта команда делает всё то же самое, что и предыдущая, только результирующий каталог будет назван myoap.
+
+### Запись изменений в репозиторий
+
+Итак, у вас имеется Git-­репозиторий и рабочая копия файлов для некоторого проекта. Вам нужно делать некоторые изменения и фиксировать “снимки” состояния (snapshots) этих изменений в вашем репозитории каждый раз, когда проект достигает состояния, которое вам хотелось бы сохранить.
+
+![](/img/skv_03.png)
+
+Запомните, каждый файл в вашем рабочем каталоге может находиться в одном из двух состояний: под версионным контролем (отслеживаемые) и нет (неотслеживаемые). Отслеживаемые файлы — это те файлы, которые были в последнем слепке состояния проекта (snapshot); они могут быть неизменёнными, изменёнными или подготовленными к коммиту (staged). Неотслеживаемые файлы — это всё остальное, любые файлы в вашем рабочем каталоге, которые не входили в ваш последний слепок состояния и не подготовлены к коммиту. Когда вы впервые клонируете репозиторий, все файлы будут отслеживаемыми и неизменёнными, потому что вы только взяли их из хранилища (checked them out) и ничего пока не редактировали.
+
+Как только вы отредактируете файлы, Git будет рассматривать их как изменённые, т.к. вы изменили их с момента последнего коммита. Вы индексируете (stage) эти изменения и затем фиксируете все индексированные изменения, а затем цикл повторяется.
+
+#### Определение состояния файлов
+
+Основной инструмент, используемый для определения, какие файлы в каком состоянии находятся — это команда ``git status``. Если вы выполните эту команду сразу после клонирования, вы увидите что­то вроде этого:
+
+```
+git status
+On branch master
+nothing to commit, working directory clean
+```
+
+Это означает, что у вас чистый рабочий каталог, другими словами — в нём нет отслеживаемых изменённых файлов. Git также не обнаружил неотслеживаемых файлов, в противном случае они бы были перечислены здесь. И наконец, команда сообщает вам на какой ветке (branch) вы сейчас находитесь. Пока что это всегда ветка master — это ветка по умолчанию (про ветки мы поговорим ниже).
+
+#### Отслеживание новых файлов
+
+Для того чтобы начать отслеживать (добавить под версионный контроль) новый файл, используется команда ``git add``. Например, чтобы начать отслеживание файла README, вы можете выполнить следующее:
+
+```
+git add README
+```
+
+Если вы выполните команду status, то увидите, что файл README теперь отслеживаемый и индексированный:
+
+```
+git status
+On branch master
+
+Changes to be committed:
+    (use "git reset HEAD <file>..." to unstage)
+
+    new file: README
+```
+
+Вы можете видеть, что файл проиндексирован по тому, что он находится в секции “Changes to be committed”. Если вы выполните коммит в этот момент, то версия файла, существовавшая на момент выполнения вами команды ``git add``, будет добавлена в историю снимков состояния. Как вы помните, когда вы ранее выполнили ``git init``, вы затем выполнили git add (файлы) — это было сделано для того, чтобы добавить файлы в вашем каталоге под версионный контроль. Команда git add принимает параметром путь к файлу или каталогу, если это каталог, команда рекурсивно добавляет (индексирует) все файлы в данном каталоге.
+
+Для добавления всех файлов проекта можно выполнить команду
+
+```
+git add .
+```
+
+Она проиндексирует все новые и изменившиеся файлы
+
+#### Индексация изменённых файлов
+
+Давайте модифицируем файл, уже находящийся под версионным контролем. Если вы измените отслеживаемый файл benchmarks.rb и после этого снова выполните команду status, то результат будет примерно следующим:
+
+```
+git status
+On branch master
+
+Changes to be committed:
+    (use "git reset HEAD <file>..." to unstage)
+
+    new file: README
+
+Changes not staged for commit:
+    (use "git add <file>..." to update what will be committed)
+
+    modified: benchmarks.rb
+```
+
+Файл benchmarks.rb находится в секции “Changes not staged for commit” — это означает, что отслеживаемый файл был изменён в рабочем каталоге, но пока не проиндексирован. Чтобы проиндексировать его, необходимо выполнить команду ``git add`` (это многофункциональная команда, она используется для добавления под версионный контроль новых файлов, для индексации изменений, а также для других целей, например для указания файлов с исправленным конфликтом слияния). Выполним ``git add``, чтобы проиндексировать benchmarks.rb, а затем снова выполним ``git status``:
+
+```
+git add benchmarks.rb
+git status
+
+On branch master
+
+Changes to be committed:
+    (use "git reset HEAD <file>..." to unstage)
+
+    new file: README
+    modified: benchmarks.rb
+```
+
+Теперь оба файла проиндексированы и войдут в следующий коммит. В этот момент вы, предположим, вспомнили одно небольшое изменение, которое вы хотите сделать в benchmarks.rb до фиксации. Вы открываете файл, вносите и сохраняете необходимые изменения и вроде бы готовы к коммиту. Но давайте ещё раз выполним ``git status``:
+
+```
+git status
+On branch master
+
+Changes to be committed:
+    (use "git reset HEAD <file>..." to unstage)
+
+    new file: README
+    modified: benchmarks.rb
+
+Changes not staged for commit:
+    (use "git add <file>..." to update what will be committed)
+
+    modified: benchmarks.rb
+```
+
+Теперь benchmarks.rb отображается как проиндексированный и непроиндексированный одновременно. Как такое возможно? Такая ситуация наглядно демонстрирует, что Git индексирует файл в точности в том состоянии, в котором он находился, когда вы выполнили команду ``git add``. Если вы выполните коммит сейчас, то файл benchmarks.rb попадёт в коммит в том состоянии, в котором он находился, когда вы последний раз выполняли команду ``git add``, а не в том, в котором он находится в вашем рабочем каталоге в момент выполнения ``git commit``. Если вы изменили файл после выполнения ``git add``, вам придётся снова выполнить ``git add``, чтобы проиндексировать последнюю версию файла:
+
+```
+git add benchmarks.rb 
+git status
+On branch master
+
+Changes to be committed:
+    (use "git reset HEAD <file>..." to unstage)
+
+    new file: README
+    modified: benchmarks.rb
+```
+
+#### Игнорирование файлов
+
+Зачастую, у вас имеется группа файлов, которые вы не только не хотите автоматически добавлять в репозиторий, но и видеть в списках неотслеживаемых. К таким файлам обычно относятся автоматически генерируемые файлы (различные логи, результаты сборки программ и т.п.). В таком случае, вы можете создать в корне проекта файл *.gitignore* с перечислением шаблонов соответствующих таким файлам. Вот пример файла *.gitignore*:
+
+```
+*.[oa] 
+*~
+```
+
+Первая строка предписывает Git'у игнорировать любые файлы заканчивающиеся на .o или .a — объектные и архивные файлы, которые могут появиться во время сборки кода. Вторая строка предписывает игнорировать все файлы заканчивающиеся на тильду (~), которая используется во многих текстовых редакторах, например Emacs, для обозначения временных файлов. Вы можете также включить каталоги log, tmp или pid; автоматически создаваемую документацию; и т.д. и т.п. 
+
+Хорошая практика заключается в настройке файла *.gitignore* до того, как начать серьёзно работать, это защитит вас от случайного добавления в репозиторий файлов, которых вы там видеть не хотите.
+
+#### Фиксация изменений
+
+Теперь, когда ваш индекс настроен так, как вам и хотелось, вы можете зафиксировать свои изменения. Запомните, всё, что до сих пор не проиндексировано — любые файлы, созданные или изменённые вами, и для которых вы не выполнили ``git add`` после момента редактирования — не войдут в этот коммит. Они останутся изменёнными файлами на вашем диске. В нашем случае, когда вы в последний раз выполняли ``git status``, вы видели что всё проиндексировано, и вот, вы готовы к коммиту. Простейший способ зафиксировать изменения — это набрать ``git commit``:
+
+```
+git commit
+```
+
+Эта команда откроет выбранный вами текстовый редактор. (Редактор устанавливается системной переменной $EDITOR — обычно это vim или emacs, хотя вы можете установить ваш любимый с помощью команды ``git config --­­global core.editor``).
+
+В редакторе будет отображён следующий текст (это пример окна Vim'а):
+
+```
+#Please enter the commit message for your changes. Lines starting
+#with '#' will be ignored, and an empty message aborts the commit.
+#On branch master
+#Changes to be committed:
+#(use "git reset HEAD <file>..." to unstage)
+#new file: README
+#modified: benchmarks.rb
+```
+
+Вы можете видеть, что комментарий по умолчанию для коммита содержит закомментированный результат работы команды ``git status``. Вы можете удалить эти комментарии и набрать своё сообщение или же оставить их для напоминания о том, что вы фиксируете. (Для ещё более подробного напоминания, что же именно вы поменяли, можете передать аргумент ­v в команду ``git commit``. Это приведёт к тому, что в комментарий будет также помещена дельта/diff изменений, таким образом вы сможете точно увидеть всё, что сделано.) Когда вы выходите из редактора, Git создаёт для вас коммит с этим сообщением (удаляя комментарии и вывод diff'а).
+
+Есть и другой способ — вы можете набрать свой комментарий к коммиту в командной строке вместе с командой commit, указав его после параметра ­m, как в следующем примере:
+
+```
+git commit -­m "Story 182: Fix benchmarks for speed"
+
+[master]: created 463dc4f: "Fix benchmarks for speed" 2 files changed, 3 insertions(+), 0 deletions(­)
+create mode 100644 README
+```
+
+Итак, вы создали коммит! Вы можете видеть, что коммит вывел вам немного информации о себе: на какую ветку вы выполнили коммит (master), какая контрольная сумма SHA­1 у этого коммита (463dc4f), сколько файлов было изменено, а также статистику по добавленным/удалённым строкам в этом коммите.
+
+Запомните, что коммит сохраняет снимок состояния вашего индекса. Всё, что вы не проиндексировали, так и остается в рабочем каталоге как изменённое; вы можете сделать ещё один коммит, чтобы добавить эти изменения в репозиторий. Каждый раз, когда вы делаете коммит, вы сохраняете снимок состояния вашего проекта, который позже вы можете восстановить или с которым можно сравнить текущее состояние.
+
+#### Просмотр истории коммитов
+
+После того как вы создадите несколько коммитов, или же вы склонируете репозиторий с уже существующей историей коммитов, вы, вероятно, захотите оглянуться назад и узнать, что же происходило с этим репозиторием. Наиболее простой и в то же время мощный инструмент для этого — команда ``git log``. Данные примеры используют очень простой проект, названный simplegit. Чтобы получить этот проект, выполните:
+
+```
+git clone git://github.com/schacon/simplegit­progit.git
+```
+
+В результате выполнения ``git log`` в данном проекте, вы должны получить что­то вроде этого: 
+
+```
+git log
+
+commit ca82a6dff817ec66f44342007202690a93763949 Author: Scott Chacon <schacon@gee­mail.com> Date: Mon Mar 17 21:52:11 2008 ­0700
+
+changed the version number
+
+commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Author: Scott Chacon <schacon@gee­mail.com> Date: Sat Mar 15 16:40:33 2008 ­0700
+
+removed unnecessary test code
+
+commit a11bef06a3f659402fe7563abf99ad00de2209e6 Author: Scott Chacon <schacon@gee­mail.com> Date: Sat Mar 15 10:31:28 2008 ­0700
+
+first commit
+```
+
+По умолчанию, без аргументов, ``git log`` выводит список коммитов созданных в данном репозитории в обратном хронологическом порядке. То есть самые последние коммиты показываются первыми. Как вы можете видеть, эта команда отображает каждый коммит вместе с его контрольной суммой SHA­1, именем и электронной почтой автора, датой создания и комментарием.
+
+Существует великое множество параметров команды ``git log`` и их комбинаций, для того чтобы показать вам именно то, что вы ищете.
+
+### Отмена изменений
+
+На любой стадии может возникнуть необходимость что­либо отменить. Здесь мы рассмотрим несколько основных инструментов для отмены произведённых изменений. Будьте осторожны, ибо не всегда можно отменить сами отмены. Это одно из немногих мест в Git'е, где вы можете потерять свою работу если сделаете что­то неправильно.
+
+#### Изменение последнего коммита
+
+Одна из типичных отмен происходит тогда, когда вы делаете коммит слишком рано, забыв добавить какие­то файлы, или напутали с комментарием к коммиту. Если вам хотелось бы сделать этот коммит ещё раз, вы можете выполнить commit с опцией --­­amend:
+
+```
+git commit --­­amend
+```
+
+Эта команда берёт индекс и использует его для коммита. Если после последнего коммита не было никаких изменений (например, вы запустили приведённую команду сразу после предыдущего коммита), то состояние проекта будет абсолютно таким же и всё, что вы измените, это комментарий к коммиту.
+
+Появится всё тот же редактор для комментариев к коммитам, но уже с введённым комментарием к последнему коммиту. Вы можете отредактировать это сообщение так же, как обычно, и оно перепишет предыдущее.
+
+Для примера, если после совершения коммита вы осознали, что забыли проиндексировать изменения в файле, которые хотели добавить в этот коммит, вы можете сделать что­то подобное:
+
+```
+git commit -­m 'initial commit'
+git add forgotten_file
+git commit --­­amend
+```
+
+Все три команды вместе дают один коммит — второй коммит заменяет результат первого.
+
+### Работа с удалёнными репозиториями
+
+>*Удаленный* тут надо понимать не как стертый (deleted) а как находящийся где-то в сети (remote)
+
+Чтобы иметь возможность совместной работы над каким­-либо Git-­проектом, необходимо знать, как управлять удалёнными репозиториями. Удалённые репозитории — это модификации проекта, которые хранятся в интернете или ещё где­-то в сети. Их может быть несколько, каждый из которых, как правило, доступен для вас либо только на чтение, либо на чтение и запись. Совместная работа включает в себя управление удалёнными репозиториями и помещение (push) и получение (pull) данных в и из них тогда, когда нужно обменяться результатами работы. Управление удалёнными репозиториями включает умение добавлять удалённые репозитории, удалять те из них, которые больше не действуют, умение управлять различными удалёнными ветками и определять их как отслеживаемые (tracked) или нет и прочее. Данный раздел охватывает все перечисленные навыки по управлению удалёнными репозиториями.
+
+Например, в рамках нашего курса вы можете дополнительно к git-серверу колледжа (GOGS) добавить личный репозиторий на github, чтобы иметь возможность доделать лабы дома.
+
+#### Отображение удалённых репозиториев
+
+Чтобы просмотреть, какие удалённые серверы у вас уже настроены, следует выполнить команду ``git remote``. Она перечисляет список имён-­сокращений для всех уже указанных удалённых дескрипторов. Если вы склонировали ваш репозиторий, у вас должен отобразиться, по крайней мере, **origin** — это имя по умолчанию, которое Git присваивает серверу, с которого вы склонировали:
+
+```
+git clone git://github.com/schacon/ticgit.git
+
+Initialized empty Git repository in /private/tmp/ticgit/.git/ remote: Counting objects: 595, done.
+
+remote: Compressing objects: 100% (269/269), done. remote: Total 595 (delta 255), reused 589 (delta 253)
+
+Receiving objects: 100% (595/595), 73.31 KiB | 1 KiB/s, done. Resolving deltas: 100% (255/255), done.
+
+cd ticgit
+git remote 
+
+origin
+```
+
+Чтобы посмотреть, какому URL соответствует сокращённое имя в Git, можно указать команде опцию ­``-v``:
+
+```
+git remote -­v
+
+origin git://github.com/schacon/ticgit.git (fetch) 
+origin git://github.com/schacon/ticgit.git (push)
+```
+
+Если у вас больше одного удалённого репозитория, команда покажет их все. Например, мой репозиторий Grit выглядит следующим образом.
+
+```
+git remote -­v
+
+bakkdoor git://github.com/bakkdoor/grit.git 
+cho45 git://github.com/cho45/grit.git 
+defunkt git://github.com/defunkt/grit.git 
+koke git://github.com/koke/grit.git 
+origin git@github.com:mojombo/grit.git
+```
+
+Это означает, что мы легко можем получить изменения от любого из этих репозиториев.
+
+#### Добавление удалённых репозиториев
+
+В предыдущих разделах мы упомянули и немного продемонстрировали добавление удалённых репозиториев, сейчас мы рассмотрим это более детально. Чтобы добавить новый удалённый Git-­репозиторий под именем-­сокращением, к которому будет проще обращаться, выполните команду ``git remote add [сокращение] [url]``:
+
+```
+git remote 
+origin
+
+git remote add pb git://github.com/paulboone/ticgit.git 
+
+git remote -­v
+
+origin git://github.com/schacon/ticgit.git 
+pb git://github.com/paulboone/ticgit.git
+```
+
+Теперь вы можете использовать в командной строке имя pb вместо полного URL. Например, если вы хотите извлечь (fetch) всю информацию, которая есть в репозитории Павла, но нет в вашем, вы можете выполнить ``git fetch pb``:
+
+```
+git fetch pb
+remote: Counting objects: 58, done.
+remote: Compressing objects: 100% (41/41), done. remote: Total 44 (delta 24), reused 1 (delta 0) Unpacking objects: 100% (44/44), done.
+From git://github.com/paulboone/ticgit
+* [new branch]
+master
+­> pb/master
+* [new branch]
+ticgit
+­> pb/ticgit
+```
+
+Ветка master Павла теперь доступна локально как pb/master. Вы можете слить (merge) её в одну из своих веток или перейти на эту ветку, если хотите её проверить.
+
+#### Fetch и Pull
+
+Как вы только что узнали, для получения данных из удалённых проектов, следует выполнить ``git fetch [имя удал. сервера]``
+
+Данная команда связывается с указанным удалённым проектом и забирает все те данные проекта, которых у вас ещё нет. После того как вы выполнили команду, у вас должны появиться ссылки на все ветки из этого удалённого проекта. Теперь эти ветки в любой момент могут быть просмотрены или слиты.
+
+Когда вы клонируете репозиторий, команда clone автоматически добавляет этот удалённый репозиторий под именем origin. Таким образом, ``git fetch origin`` извлекает все наработки, отправленные (push) на этот сервер после того, как вы склонировали его (или получили изменения с помощью fetch). Важно отметить, что команда fetch забирает данные в ваш локальный репозиторий, но не сливает их с какими-­либо вашими наработками и не модифицирует то, над чем вы работаете в данный момент. Вам необходимо вручную слить эти данные с вашими, когда вы будете готовы.
+
+Если у вас есть ветка, настроенная на отслеживание удалённой ветки, то вы можете использовать команду ``git pull``. Она автоматически извлекает и затем сливает данные из удалённой ветки в вашу текущую ветку. Этот способ может для вас оказаться более простым или более удобным. К тому же по умолчанию команда ``git clone`` автоматически настраивает вашу локальную ветку master на отслеживание удалённой ветки master на сервере, с которого вы клонировали (подразумевается, что на удалённом сервере есть ветка master). Выполнение ``git pull``, как правило, извлекает (fetch) данные с сервера, с которого вы изначально склонировали, и автоматически пытается слить (merge) их с кодом, над которым вы в данный момент работаете.
+
+#### Push
+
+Когда вы хотите поделиться своими наработками, вам необходимо отправить (push) их в главный репозиторий. Команда для этого действия простая: ``git push [удал. сервер] [ветка]``. Чтобы отправить вашу ветку master на сервер origin (повторимся, что клонирование, как правило, настраивает оба этих имени автоматически), вы можете выполнить следующую команду для отправки наработок на сервер:
+
+```
+git push origin master
+```
+
+Эта команда срабатывает только в случае, если вы клонировали с сервера, на котором у вас есть права на запись, и если никто другой с тех пор не выполнял команду push. Если вы и кто-­то ещё одновременно клонируете, затем он выполняет команду push, а затем команду push выполняете вы, то ваш push точно будет отклонён. Вам придётся сначала вытянуть (pull) их изменения и объединить с вашими. Только после этого вам будет позволено выполнить push.
+
+#### Инспекция удалённого репозитория
+
+Если хотите получить побольше информации об одном из удалённых репозиториев, вы можете использовать команду ``git remote show [удал. сервер]``. Если вы выполните эту команду с некоторым именем, например, origin, вы получите что­то подобное:
+
+```
+git remote show origin 
+* remote origin
+URL: git://github.com/schacon/ticgit.git
+Remote branch merged with 'git pull' while on branch master master
+Tracked remote branches master
+ticgit
+```
+
+Она выдаёт URL удалённого репозитория, а также информацию об отслеживаемых ветках. Эта команда любезно сообщает вам, что если вы, находясь на ветке master, выполните ``git pull``, ветка master с удалённого сервера будет автоматически влита в вашу сразу после получения всех необходимых данных. Она также выдаёт список всех полученных ею ссылок.
+
+Это был пример для простой ситуации, и наверняка вы встретились с чем-­то подобным. Однако, если вы используете Git более интенсивно, вы можете увидеть гораздо большее количество информации от ``git remote show``:
+
+```
+git remote show origin
+*remote origin
+URL: git@github.com:defunkt/github.git
+Remote branch merged with 'git pull' while on branch issues issues
+Remote branch merged with 'git pull' while on branch master master
+New remote branches (next fetch will store in remotes/origin) caching
+Stale tracking branches (use 'git remote prune') libwalker
+walker2
+Tracked remote branches acl
+apiv2
+dashboard2 issues master postgres
+Local branch pushed with 'git push' master:master
+```
+
+Данная команда показывает какая именно локальная ветка будет отправлена на удалённый сервер по умолчанию при выполнении ``git push``. Она также показывает, каких веток с удалённого сервера у вас ещё нет, какие ветки всё ещё есть у вас, но уже удалены на сервере. И для нескольких веток показано, какие удалённые ветки будут в них влиты при выполнении ``git pull``.
+
+#### Удаление и переименование удалённых репозиториев
+
+Для переименования ссылок в новых версиях Git'а можно вылолнить команду ``git remote rename``, это изменит сокращённое имя, используемое для удалённого репозитория. Например, если вы хотите переименовать pb в paul, вы можете сделать это следующим образом:
+
+```
+git remote rename pb paul
+git remote
+origin
+paul
+```
+
+Стоит упомянуть, что это также меняет для вас имена удалённых веток. То, к чему вы обращались как ``pb/master``, стало ``paul/master``.
+
+Если по какой­-то причине вы хотите удалить ссылку (вы сменили сервер или больше не используете определённое зеркало, или, возможно, контрибьютор перестал быть активным), вы можете использовать ``git remote rm``:
+
+```
+git remote rm paul 
+git remote
+origin
+```
+
+## Задания для самостоятельной работы
+
+Пройтись по пунктам 1-23 [этой][1] инструкции (до ветвлений)
+
+[содержание](/readme.md)  
+
+[1]: https://githowto.com/ru/setup
+
+[_]: https://studfile.net/preview/6845207/
+[_]: http://uii.mpei.ru/study/courses/sdt/16/lab02_vcs.task.pdf

+ 31 - 0
articles/lab-templates.md

@@ -0,0 +1,31 @@
+[содержание](/readme.md)  
+
+Задания для лабораторной работы
+
+>Реализовать задание, номер которого равен последней цифре вашего телефона
+
+0. Шаблон **Стратегия**. Проект *Принтеры*. В проекте должны быть реализованы разные модели принтеров, которые выполняют разные виды печати.
+
+1. Шаблон **Наблюдатель**. Проект *Оповещение постов ГАИ*. В проекте должна быть реализована отправка сообщений всем постам ГАИ.
+
+2. Шаблон **Декоратор**. Проект *Универсальная электронная карта*. В проекте должна быть реализована универсальная электронная карта, в которой есть функции паспорта, страхового полиса, банковской карты и т. д.
+
+3. Шаблон **Фабричный метод**. Проект *Фабрика смартфонов*. В проекте должно быть реализовано создание смартфонов с различными характеристиками.
+
+4. Шаблон **Абстрактная фабрика**. Проект *Заводы по производству автомобилей*. В проекте должно быть реализована возможность создавать автомобили различных типов на разных заводах.
+
+5. Шаблон **Команда**. Проект *Клавиатура настраимаемого калькулятора*. Цифровые и арифметические кнопки имеют фиксированную функцию, а остальные могут менять своё назначение.
+
+6. Шаблон **Адаптер**. Проект *Часы*. В проекте должен быть реализован адаптер, который дает возможность пользоваться часами со стрелками так же, как и цифровыми часами. В классе *Часы со стрелками* хранятся повороты стрелок.
+
+7. Шаблон **Фасад**. Проект *Компьютер*. В проекте должен быть реализован “компьютер”, который выполняет основные функции, к примеру, включение, выключение, запуск ОС, запуск программы, и т.д, не раскрывая клиенту деталей выполнения этой операции.
+
+8. Шаблон **Строитель**, применяется когда у объекта есть множество однотипных полей, при задании значений которых их легко перепутать. Реализуйте класс *контакты*, в котором может быть много полей: имя, фамилия, адрес, телефон и т.п.
+
+9. Шаблон **Мост**. Реализовать проект *устройства и пульты управления*. Есть несколько устройств, реализующих методы *включение*, *громкость*, *канал* и несколько пультов управления: *стандартный* (звук и каналы меняются на +-1), расширенный (номер канала можно указать явно, есть отдельная кнопка "mute" для отключения звука)
+
+[содержание](/readme.md)  
+
+[_]: http://grazit.ru/laboratornaya-rabota-4-shabloni-proektirovaniya.html?page=7
+
+[_]: https://docplayer.ru/109228240-Patterny-programmirovaniya.html

+ 149 - 0
articles/lab1.md

@@ -0,0 +1,149 @@
+# Лабораторная работа №1 (тема №4)
+
+## Знакомство со средой программирования и структурой проекта. 
+
+>Ссылка на скачивание и компоненты для устрановки описаны в [лекции](./t2l1#интегрированная-среда-программирования)
+
+1. Запустите **Visual Stidio 2022** и выберите пункт _Создание проекта_
+
+    ![](../img/04013.png)
+
+1. Установите фильтры и найдите шаблон проекта _Консольное приложение (Макрософт)_
+
+    >Обратите внимание, не .NET Framework, а просто .NET
+
+    ![](../img/04014.png)
+
+    В Настройках нового проекта введите название и запомните/поменяйте, если нужно, расположение проекта
+
+    ![](../img/04015.png)
+
+    >У вас будет один репозиторий на все лабораторные работы в рамках этого курса, поэтому номер лабораторной в названии не нужен. Переключаться между лабораторными работами мы будем с помощью *веток* GIT-а
+
+На основном экране проекта у нас расположен код программы, который система сгенерировала для нас автоматически:
+
+![](../img/lab4_01.png)
+
+>В **Visual Studio** можно открыть окно консоли (на скриншоте оно снизу-слева). Открыть можно через меню _Вид - Консоль_ или комбинацией клавишь Ctrl+\`
+
+В каталоге проекта есть директории `.vs`, `bin` и `obj`, которые содержат скомпилированную программу и настройки и, значит, сохранять эти каталоги в репозитории не нужно (добавить в `.gitignore`).
+
+## Создание репозитория с ветками. 
+
+Сначала создайте новый репозиторий и инициализируйте его в верхнем каталоге **oap_labs** (в котором находится файл `*.sln`). В **Visual Studio** есть каталог **решения** и в нём одноименный каталог **проекта** (в одном решении может быть несколько проектов)
+
+Затем создайте в корне репозитория файл `.gitignore`, в который добавьте каталоги, которые не нужно сохранять в репозитории:
+
+```.gitignore
+*/bin/
+*/obj/
+.vs
+```
+
+Знак "*" перед названиями каталогов `bin` и `obj` обозначает "любой каталог", т.е. этот фильтр будет работать не зависимо от названия вашего проекта.
+
+После того, как мы сохраним файл, будет видно, что перечисленные каталоги больше не отслеживаются GIT-ом:
+
+![](../img/04017.png)
+
+Создайте файл `readme.md` (в корне "решения") с описанием проекта.
+
+Сохраните текущее состояние проекта в репозитории
+
+Теперь создайте ветку *lab1* и переключитесь на неё:
+
+```
+git checkout -b lab4_1
+```
+
+Таким образом, у вас в главной ветке будет пустой шаблон приложения с описанием, а в ветках реализации для отдельных лабораторных.
+
+## Составление программ линейной структуры.
+
+Например, "вычислить периметр и площадь прямоугольного треугольника по длинам двух катетов" (размер катетов получить из консоли)
+
+Мы ещё не проходили команды работы с консолью, поэтому приведу кусок кода с комментариями:
+
+```cs
+// команда Console.Write выводит текст в консоль
+Console.Write("Input katet1: ");
+
+// команда Console.ReadLine читает СТРОКУ из консоли
+var katet1 = Console.ReadLine();
+
+Console.Write("Input katet2: ");
+var katet2 = Console.ReadLine();
+
+// команда Math.Sqrt - квадратный корень
+// Math.Pow - возведение в степень
+// Convert.ToDouble - преобразует строку в число
+var gipotenuza = Math.Sqrt(
+    Math.Pow(
+        Convert.ToDouble(katet1), 2) + 
+    Math.Pow(
+        Convert.ToDouble(katet2), 2));
+
+// выводим результат
+// знак $ перед строкой указывает, 
+// что внутри строки в фигурных скобках названия переменных
+Console.WriteLine(
+    $"Gipotenuza = {gipotenuza}");
+
+// читаем строку, чтобы консольное окно сразу не закрылось
+Console.Write("Press ENTER to continue...");
+Console.ReadLine();
+```
+
+В некоторых заданиях требуется ввести числа в одной строке (например, "2 2"), но т.к. функция **ReadLine** возвращает **строку**, то такая строка будет не валидным числом.
+
+Для разбиения строки на подстроки можно воспользоваться методом **Split**. Этот метод делит строку на массив строк, используя переданный символ как разделитель строк:
+
+```cs
+var numbers = Console.ReadLine();
+// numbers = "2 2"
+
+var numberList = numbers.Split(' ');
+// numberList = ["2","2"]
+
+var katet1 = Convert.ToInt32(numberList[0]);
+var katet2 = Convert.ToInt32(numberList[1]);
+```
+
+---
+
+## Задание
+
+1. Выполните задание, выданное преподавателем
+
+    Задания берите с сайта [Школа программиста](https://acmp.ru/index.asp?main=tasks), из темы "Задачи для начинающих" 
+
+    Чем больше решите, тем лучше, но желательно от 3 заданий (на "троечку")
+
+1. Опубликуйте результаты в удаленном репозитории и скиньте ссылку преподавателю.
+
+## Пример выполнения
+
+> Задача: [A+B](https://acmp.ru/index.asp?main=task&id_task=1)
+>
+> Требуется сложить два целых числа А и В.
+>
+> Решение:
+>
+>```cs
+>Console.Write(
+>    "Введите два целых числа через пробел: ");
+>var inputString = Console.ReadLine();
+>var stringParts = inputString.Split(' ');
+>Console.WriteLine(
+>    Convert.ToInt32(stringParts[0]) + 
+>    Convert.ToInt32(stringParts[1]));
+>```
+>
+>Результат работы:
+>
+>```
+>Введите два целых числа через пробел: 2 3
+>5
+>
+>Process finished with exit code 0.
+>```

+ 84 - 0
articles/lab2.md

@@ -0,0 +1,84 @@
+# Лабораторная работа "Составление программ разветвляющейся структуры"
+
+Напоминаю, что ветвление в **C#** в общем виде выглядит так:
+
+```cs
+if (условие)
+{
+  оператор
+}
+else if (другое условие)
+{
+
+} 
+else
+{
+  оператор
+}
+```
+
+или так:
+
+```cs
+switch(выражение) {
+    case константа1:
+        последовательность операторов
+        break;
+    case константа2:
+        последовательность операторов
+        break;
+    case константаЗ:
+        последовательность операторов
+        break;
+
+    ...
+
+    default:
+        последовательность операторов
+        break;
+}
+```
+
+или даже так:
+
+```
+[val result =] ЛогическоеВыражение ? ВыражениеЕсли : ВыражениеИначе;
+```
+
+## Задание
+
+1. Введите три числа. Возведите в квадрат те из них, значения которых неотрицательны, и в четвертую степень - остальные
+1. Введите координаты двух точек и определите, которая из них ближе к началу координат.
+1. Введите два угла треугольника (в градусах). Определите, существует ли такой треугольник, и если да, то будет ли он прямоугольным.
+1. Введите два числа не равные друг другу. Меньшее из них заменить половиной их суммы, а большее - их удвоенным произведением.
+1. Введите координаты точки на плоскости. Определите где она расположена (на какой оси или в каком координатном углу).
+1. Введите дату. Определите её правильность (число от 1 до 31, месяц от 1 до 12).
+1. Введите три числа. Найдите сумму большего и меньшего из них.
+1. Введите координаты точки и радиус окружности. Определите, входит ли точка в окружность (центр окружности в начале координат).
+1. Введите координаты точки D. Проверьте, принадлежит ли она треугольнику. Координаты вершин треугольника (A, B, C) задайте константами.
+
+    Есть несколько вариантов решения, приведу простой, через площади треугольников:
+
+    ![](../img/lab2_01.bmp)
+
+    Если площадь треугольника `ABC` меньше, чем сумма площадей треугольников `ADC`, `ABD`, `BDC`, то точка `D` снаружи треугольника.
+
+1. Введите три числа. Определите, можно ли построить треугольник со сторонами, длины которых равны этим числам. Если возможно, то определить, является ли этот треугольник остроугольным.
+
+---
+
+>Делаем аналогично лабораторной про линейные алгоритмы: 
+>
+># Название лабораторной
+>
+>## Номер задачи
+>
+>```
+>код на C#
+>```
+>
+>```
+>результат работы
+>```
+>
+>...

+ 21 - 0
articles/lab3.md

@@ -0,0 +1,21 @@
+# Тема массивы и циклы
+
+## Задание
+
+Выполните не менее 5 заданий на сайте [школа программиста](https://acmp.ru/index.asp?main=tasks) из категории "Двумерные массивы".
+
+При оформлении результата добавьте в `readme.md`:
+
+1. Название лабораторной
+1. Текст задания
+1. Блоки кода программы и результата работы программы
+
+    Блоки кода в **markdown** оформляются тройными обратными кавычками в начале и конце, например:
+
+    \`\`\`
+    ```
+    Содержимое блока кода
+    ```
+    \`\`\`
+
+    После открывающих кавычек может указываться формат данных, содержащихся в блоке кода. Для языка **C#** это \`\`\`cs

+ 18 - 0
articles/lab4.md

@@ -0,0 +1,18 @@
+# Функции. Исключения.
+
+1. Реализовать функцию умножения целых чисел (с учетом знака) не используя операцию умножения.
+
+2. Реализовать функцию деления целых чисел (с учетом знака) не используя операцию деления.
+
+3. Отсортировать массив по сумме цифр чисел (т.е. 11 = 1+1 = 2). Сортировать **методом пузырька**.
+    >Сортировка методом пузырька заключается в том, что по массиву осуществляются множественные проходы. На каждом проходе очередной элемент сравнивается со следующим за ним. И если он больше (при сортировке по возрастанию), то элементы массива меняются местами.
+    >
+    >Таким образом при первом проходе по массиву при сортировке по возрастанию последним в массиве оказывается самое большое значение. При следующем проходе на предпоследнем месте окажется максимальное из оставшихся чисел. Сравнивать последнее и предпоследнее числа нет смысла. Поэтому количество просматриваемых элементов массива на каждом проходе сокращается на 1. Количество проходов равно количеству элементов массива за вычетом единицы, т.к. происходит попарное сравнение.
+
+4. Бинарный поиск в массиве. Пользователь вводит число. Сообщить, есть ли оно в массиве (сгенерировать случайным образом и отсортировать используя алгоритм предыдущего задания), а также, если есть, в каком месте находится. При решении задачи использовать бинарный (двоичный) поиск, который оформить в виде отдельной функции.
+   
+5. Написать функцию, которая вычисляет среднее арифметическое элементов массива, переданного ей в качестве аргумента.
+
+6. Написать функцию, которая ищет делители числа (функция возвращает массив делителей).
+
+7. Во все предыдущие функции добавить перехват и генерацию исключений.

+ 80 - 0
articles/lab4_4.md

@@ -0,0 +1,80 @@
+# Работа со строковыми переменными.
+
+## Напоминаю теорию
+
+Строка, как и всё в **C#**, является объектом и у неё есть методы (более полный список можно посмотреть в [лекции](./4_prog_string.md) или интернете):
+
+* _Length_ - длина строки
+
+    ```cs
+    var stroka = "test"
+    Console.WriteLine(
+        "Длина строки - {0}",
+        stroka.Length);
+    ```    
+
+* _Contains_ - строка содержит подстроку
+
+    ```cs
+    if(someString.Contains(subString)){
+        ...
+    }
+    ```
+
+* _StartsWith_, _EndsWith_ - строка начинается/заканчивается подстрокой
+
+* _IndexOf_ - возвращает позицию подстроки в строке (Если искомый символ или подстрока не обнаружены, то возвращается значение `-1`)
+
+* _Split_ - разделяет строку на массив строк. В примере ниже строка чисел разделённых пробелом преобразуется массив. Этим методом удобно разбирать параметры полученные методом `Console.ReadLine();`.
+
+    ```cs
+    var stringList = "1 2 3 4 5".Split([' ']);
+    ```
+
+* _ToUpper_, _ToLower_ - смена регистра
+
+* _Substring_ - Получение подстроки из строки
+
+Элемент строки (символ) можно получить по индексу (в этом смысле строка является одномерным массивом):
+
+```cs
+var stroka = "ABCDEF";
+for(int i = 0; i < stroka.Length; i++)
+{
+    Console.WriteLine("{1}-й символ - '{2}'",
+        i, stroka[i]);
+}
+```    
+
+## Задание
+
+1. Номера автобусов
+
+    Однажды Вася очень долго просидел на остановке, прежде чем дождался своего автобуса. Чтобы как-то занять время, он решил записывать на листочке государственные регистрационные номера проходящих мимо автобусов. При этом производилась запись лишь основного номера, без учета региональной принадлежности.
+
+    Основная часть государственного регистрационного номера состоит из 6 символов: трех букв и трех цифр. Сначала идет буква, затем 3 цифры и еще 2 буквы заканчивают запись. В качестве цифр могут использоваться любые цифры от 0 до 9, а в качестве букв только прописные буквы, обозначения которых присутствуют как в английском, так и в русском алфавите, т.е. только следующие символы: A, B, C, E, H, K, M, O, P, T, X, Y. Например, «P204BT» - правильный номер, а «X182YZ» и «ABC216» - нет.
+
+    Ваша задача заключается в проверке правильности проделанной Васей работы. А именно, нужно определить, какие из номеров соответствуют принятому стандарту, а какие нет.
+
+    Номера читаем в цикле из консоли. Если номер пустой (нажали ENTER), то выходим из программы.
+
+1. Сжатие бинарных последовательностей    
+
+    Последовательность из символов «0» и «1» называется бинарной. Они широко применяются в информатике и других науках. Одно из неудобств бинарных последовательностей – их трудно запоминать. Для решения этой проблемы были предложены разные способы их сжатия. Программист Слава использует следующий способ: просматривая последовательность слева направо, он заменяет «1» на «a», «01» на «b», «001» на «c», …, «00000000000000000000000001» на «z». Напишите программу, которая поможет Славе автоматизировать этот способ сжатия.
+
+1. Стрелки
+
+    Задана последовательность, состоящая только из символов ‘>’, ‘<’ и ‘-‘. Требуется найти количество стрел, которые спрятаны в этой последовательности. Стрелы – это подстроки вида ‘>>-->’ и ‘<--<<’ (наконечник стрелы, древко и оперение).
+
+1. Нумеролог
+
+    Чтобы предсказать судьбу человека, нумеролог берет время жизни человека в секундах, затем складывает все цифры этого числа. Если полученное число состоит более чем из одной цифры, операция повторяется, пока в числе не останется одна цифра. Затем по полученной цифре и числу операций, необходимых для преобразования числа в цифру нумеролог предсказывает судьбу человека. Нумеролог плохо умеет считать, а числа, с которыми он работает, могут быть очень большими. Напишите программу, которая бы делала все расчеты за него.
+
+1. Перестановка
+
+    Если Вы читали Гарри Поттера, то знаете, что повелитель зла, Лорд Волдеморт создал свое имя путем перестановки букв в своем настоящем имени. Так из имени «Tom Marvolo Riddle» он получил «I am Lord Voldemort».
+
+    Напишите программу, которая проверяет, можно ли получить из одного имени другое путем перестановки его букв. При этом регистром букв нужно пренебречь.
+
+    На входе два слова (можно в разных строках, а можно вспомнить про метод Split)
+

+ 43 - 0
articles/lab4_5.md

@@ -0,0 +1,43 @@
+# Лабораторная работа
+
+## Работа с данными типа: множество, дата, кортежи.
+
+### Множества
+
+Решить задачу из [учебника](../doc/%D0%A3%D1%87%D0%B5%D0%B1%D0%BD%D0%B8%D0%BA%20%D0%9E%D1%81%D0%BD%D0%BE%D0%B2%D1%8B%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F.pdf) по теме "Множества" (стр. 360). Номер задачи равен номеру ученика в журнале.
+
+### Даты
+
+1. Ввести с консоли строку в формате `ДД.ММ.ГГГГ`. Проверьте является ли введенная строка валидной датой.
+
+1. Найти количество дней между двумя датами. 
+
+1. Найти количество часов между двумя датами. 
+
+1. Найти количество минут между двумя датами. 
+
+1. Найти "день программиста" (256-й день в году) для указанного года (ввести с консоли). Вывести дату и день недели.
+
+1. Известен номер **n** некоторого дня года (`1 ≤ n ≤ 365`). Определить номер и название дня недели, на который
+выпадает данный день года.
+
+1. Задана дата экзамена. С клавиатуры надо вводить другую дату, в случае, если дата раньше даты экзамена, вывести "осталось n дней", если уже прошел экзамен "прошло n дней", и если экзамен сегодня "сегодня экзамен!"
+
+1. Вывести дату в формате `ДД.ММ.ГГГГ` для "завтра"
+
+1. Задан день и месяц рождения в формате `dd.mm`. Определите, сколько дней осталось/прошло с дня рождения. Если сегодня - день рождения, то вывести поздравление.
+
+1. Часы показывают время в формате `hh:mm:ss`. Определите количество секунд, которое прошло с начала суток.
+
+1. Задан неупорядоченный список дат в формате `ДД.ММ.ГГГГ`, найти ближайшую (к сегодняшнему дню) дату из списка.
+
+1. Задан неупорядоченный список дней рождений в формате `ДД.ММ.ГГГГ`. Вывести упорядоченный список месяцев с количеством дней рождений в этом месяце.
+
+1. Поиск наиболее популярного месяца: объявите массив произвольных дат в формате `ДД.ММ.ГГГГ`. Найдите самый популярный месяц. 
+
+1. Пятница, 13-е. Докажите, что 13-е число месяца чаще всего приходится на пятницу.
+Напишите программу, которая выводит на экран 7 чисел: вероятности выпадения 13 числа каждого месяца на понедельник, вторник, среду, четверг, пятницу, субботу, воскресенье.
+
+1. На одном из московских вокзалов билеты продают N касс. Каждая касса работает без перерыва определенный промежуток времени по фиксированному расписанию. Требуется определить, в какой промежуток времени работает наибольшее количество касс. (на входе в программу строка вида `00:00-01:00,00:30-02:30,...`)
+
+1. Для заданного года посчитайте количество выходных дней в этом году (то есть количество суббот и воскресений).

+ 3 - 0
articles/lab5_async.md

@@ -0,0 +1,3 @@
+# Лабораторная работа (Многопоточность)
+
+Используя примеры из [лекции](../articles/t5_thread_async.md) исследовать работу с асинхронными методами. Результат (исследуемый код и логи) оформить в отдельную ветку в репозитории и отчет о лабораторной работе.

+ 28 - 0
articles/lab5_file_types.md

@@ -0,0 +1,28 @@
+# Лабораторная работа (Типы файлов)
+
+Используя примеры из лекции [Форматы файлов](../articles/t5_file_types.md) реализовать слежующий функционал:
+
+1. Используя класс **StringReader** создать поток с CSV данными, приведенными ниже:
+
+    ```cs
+    var Source = new StringReader("Иванов Иван Иванович,01.01.2000,И-21\nПетров Петр Петрович,02.02.2002,С-21\nСидоров Сидор Сидорович,03.03.2003,И-31");
+    ```
+
+2. Считать данные из созданного потока в список используя класс TextFieldParser
+
+    ```cs
+    class Student
+    {
+        public string Name { get; set; }
+        public DateTime BirthDay { get; set; }
+        public string Group { get; set; }
+    }
+
+    ...
+
+    var StudentList = List<Student>();
+    ```
+
+3. Сохранить полученный список в файл в формате XML используя сериализацию
+
+4. В отдельной функции считать ранее созданный XML-файл и сохранить данные в файл в формате JSON используя сериализацию.

+ 5 - 0
articles/lab5_files.md

@@ -0,0 +1,5 @@
+# Лабораторная работа
+
+## Работа с файлами
+
+Используя примеры из лекции [Работа с потоками и файловой системой](../articles/t5_files.md) исследовать чтение и запись текстовых и бинарных файлов. Результат (исследуемый код и логи) оформить в отдельную ветку в репозитории и отчет о лабораторной работе.

+ 52 - 0
articles/lab5_function.md

@@ -0,0 +1,52 @@
+# Лабораторная работа "Функции (методы)"
+
+## Оформление XML-комментариев
+
+При оформлении отчета необходимо использовть [XML-комментарии](./t3l1.md#документация-xml), например:
+
+```cs
+/// <summary>
+/// Метод возвращает сумму двух целых чисел
+/// </summary>
+/// <param name="a">Первое слагаемое</param>
+/// <param name="b">Второе слагаемое</param>
+/// <example>
+/// <code>
+/// int c = Sum(2, 2);
+/// </code>
+/// </example>
+/// <returns>
+/// Сумму двух целых чисел
+/// </returns>
+static int Sum(int a, int b) => a + b;
+```
+
+## Задания
+
+1. Треугольник задан координатами своих вершин. Составить
+программу для вычисления его площади.
+
+1. Составить программу для нахождения наибольшего общего
+делителя четырех натуральных чисел.
+
+1. Составить программу для нахождения наименьшего общего
+кратного трех натуральных чисел.
+
+1. Написать программу для нахождения суммы большего и мень­шего из трех чисел.
+
+1. Составить программу, которая в массиве `A[N]` находит второе по величине число (вывести на печать число, которое мень­ше максимального элемента массива, но больше всех других элементов).
+
+1. Вася родился 12 декабря в 1991 году. В 2011 году он обнаружил, что если взять разность суммы квадратов цифр года (`1991`) его рождения и квадрата дня (`12`) рождения, то получается число, равное его возрасту (`20`). Вася захотел узнать, какие еще дни рождения `XX` века будут обладать таким же свойством в заданный N-ый год `XXI` века.
+
+1. Распаковка строки
+
+    Будем рассматривать только строчки, состоящие из заглавных английских букв. Например, рассмотрим строку `AAAABCCCCCDDDD`. Длина этой строки равна `14`. Поскольку строка состоит только из английских букв, повторяющиеся символы могут быть удалены и заменены числами, определяющими количество повторений. Таким образом, данная строка может быть представлена как `4AB5C4D`. Длина такой строки `7`. Описанный метод мы назовем упаковкой строки.
+
+    Напишите программу, которая берет упакованную строчку и восстанавливает по ней исходную строку.
+
+1. Быки и коровы
+
+    Петя и Вася часто играют в различные логические игры. Недавно Петя поведал Васе о новой игре «Быки и коровы» и теперь они играют в эту игру сутками. Суть игры очень проста: Петя загадывает четырехзначное число, состоящее из различных цифр. Вася отгадывает задуманное Петей число, перебирая возможные варианты. Каждый раз Вася предлагает вариант своего числа, а Петя делает Васе подсказку: сообщает количество быков и коров, после чего Вася с учетом подсказки продолжает отгадывание числа до тех пор, пока не отгадает. 
+    
+    Быки – это количество цифр в предложенном Васей числе, совпадающих по значению и стоящих в правильной позиции в задуманном Петей числе. Коровы – количество цифр, совпадающих по значению, но находящихся в неверной позиции. Например, если Петя задумал число `5671`, а Вася предложил вариант `7251`, то число быков равно `1` (только цифра `1` на своем месте), а число коров равно `2` (только цифры `7` и `5` не на своих местах). Петя силен в математике, но даже он может ошибаться. Помогите Пете написать программу, которая бы по загаданному Петей и предложенному Васей числам сообщала количество быков и коров.
+

+ 36 - 0
articles/lab5_regex.md

@@ -0,0 +1,36 @@
+# Регулярные выражения
+
+1. Найдите все натуральные числа (возможно, окружённые буквами);
+2. Найдите все «слова», написанные капсом (то есть строго заглавными), возможно внутри настоящих слов (аааБББввв);
+3. Найдите слова, в которых есть русская буква, а когда-нибудь за ней цифра;
+4. Найдите все слова, начинающиеся с русской или латинской большой буквы (``\b`` — граница слова);
+5. Найдите слова, которые начинаются на гласную (``\b`` — граница слова);
+6. Найдите все натуральные числа, не находящиеся внутри или на границе слова;
+7. Найдите строчки, в которых есть символ * (. — это точно не конец строки!);
+8. Найдите строчки, в которых есть открывающая и когда-нибудь потом закрывающая скобки;
+9. Выделите одним махом весь кусок оглавления (в конце примера, вместе с тегами);
+10. Выделите одним махом только текстовую часть оглавления, без тегов;
+11. Найдите пустые строчки;
+12. Найдите время
+
+    Время имеет формат часы:минуты. И часы, и минуты состоят из двух цифр, пример: 09:00. Напишите регулярное выражение для поиска времени в строке: “Завтрак в 09:00”. Учтите, что “37:98” – некорректное время.
+
+13. Цвет
+
+    Напишите регулярное выражение для поиска HTML-цвета, заданного как #ABCDEF, то есть символ "#" и затем 6 шестнадцатеричных символов.
+
+14. Разобрать арифметическое выражение
+
+    Арифметическое выражение состоит из двух чисел и операции между ними, например:
+
+    * 1 + 2
+    * 1.2 *3.4
+    * -3/ -6
+    * -2-2
+
+    Список операций: “+”, «-», “*” и “/”.
+
+    Также могут присутствовать пробелы вокруг оператора и чисел.
+
+    Напишите регулярное выражение, которое найдёт как всё арифметическое действие, так и (через группы) два операнда.
+

+ 5 - 0
articles/lab5_try_null.md

@@ -0,0 +1,5 @@
+# Лабораторная работа
+
+## Исключения. NULL.
+
+Используя примеры из [лекции](../articles/t5_exception.md) исследовать исключения и операции с нуллабельными типами данных. Результат (исследуемый код и логи) оформить в отдельную ветку в репозитории и отчет о лабораторной работе.

+ 44 - 0
articles/lab8-oop-json.md

@@ -0,0 +1,44 @@
+# ООП. Основы (JSON)
+
+Основано на [этой](./t5_file_types.md#вариант-попроще) лекции
+
+1. Создание класса
+
+    В отличие от CSV данные десериализуются в класс, т.е. этот класс должен быть объявлен заранее, например:
+
+    ```cs
+    class Student
+    {
+        public string firstName {get;set;}
+        public int age {get;set;}
+    }
+    ```
+
+2. Подготовка файла для импорта
+
+    ```json
+    [
+        {
+            "firstName": "Сергей", 
+            "age": 18
+        },
+        {
+            "firstName": "Богдан", 
+            "age": 17
+        }
+    ]
+    ```
+
+3. Импорт данных
+
+    ```cs
+    var buffer = File.ReadAllText("тут имя вашего файла");
+    var serializer = new JavaScriptSerializer();
+    var studentList = serializer.Deserialize<Student[]>(buffer);
+    ...
+    ```
+
+# Задача
+
+* сформировать файл с данными в формате JSON для вашей предметной области
+* загрузить данные с программу из подготовленного файла

+ 121 - 0
articles/lab8-oop.md

@@ -0,0 +1,121 @@
+# Лабораторная работа №6.1. ООП. Основы.
+
+>Реализовать **одно** задание, номер которого выдаст преподаватель
+
+Разработать класс для объекта. Сформировать набор данных и реализовать выборки с помощью LINQ-запросов. 
+
+Список полей и выборок приведён примерный - во всех заданиях должны быть реализованы свойства с типами **int**, **double**, **DateTime** или **TimeSpan**, **string**, **boolean** и выборки с использованием этих свойств.
+
+>Тип **TimeSpan** хранит *часы*, *минуты*, *секунды* - обычно используется для хранения результата вычитания двух дат, но можно использовать и сам по себе.
+
+В разных выборках, кроме собственно выборки данных, использовать дополнительные методы LINQ-запросов (сортировка, лимит и т.д.)
+
+Если работу с CSV ещё не проходили, то сформировать список исходных данных программно (не менее 5-ти элементов), например:
+
+```cs
+var Users = new List<User>
+{
+    new User {Name="Том", Age=23},
+    new User {Name="Боб", Age=25},
+    ...
+};
+```
+
+Переменные с типом **DateTime** и **TimeSpan** создаются с помощью конструкторов:
+
+```cs
+var BirthDay = new DateTime(2002, 1, 31);
+var DateDiff = new TimeSpan(33, 0, 0);
+```
+
+Напоминаю структуру проекта:
+
+```cs
+namespace HelloApp
+{
+    // описываем новый класс в пространстве имен (namespace)
+    class Person
+    {
+        // используем CamelCase - все слова (в переменных и методах) с большой буквы
+        public string FirstName;
+        public DateTime BirthDay;
+    }
+    // существующий класс не трогаем
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            // формируем набор данных (список объектов вашего типа)
+            var Users = new List<Person>
+            {
+                new Person {FirstName="Том", BirthDay=new DateTime(2002,1,31)},
+                new Person {FirstName="Боб", BirthDay=new DateTime(2004,12,1)}
+            };
+
+            // дальше LINQ-запросы
+        }
+    }
+}
+```
+
+1. **Student**: Фамилия, Имя, Отчество, Дата рождения, Адрес, Телефон, Факультет, Курс. Данные загрузить из CSV-файла. Вывести:
+    * список студентов заданного факультета; 
+    * списки студентов для каждого факультета и курса;
+    * список студентов, родившихся после заданного года.
+
+2. **Abiturient**: Фамилия, Имя, Отчество, Адрес, Оценки, Дата. Данные загрузить из CSV-файла. Вывести:
+
+    * список абитуриентов, имеющих неудовлетворительные оценки; 
+    * список абитуриентов, сумма баллов у которых не меньше заданной;
+    * выбрать N абитуриентов, имеющих самую высокую сумму баллов, и список абитуриентов, имеющих полупроходной балл.
+
+3. **Aeroflot**: Пункт назначения, Номер рейса, Тип самолета, Время вылета, Дни недели. Данные загрузить из CSV-файла. Вывести:
+
+    * список рейсов для заданного пункта назначения; 
+    * список рейсов для заданного дня недели;
+    * список рейсов для заданного дня недели, время вылета для которых больше заданного.
+
+4. **Book**: Автор, Название, Издательство, Год, Количество страниц. Данные загрузить из CSV-файла. Вывести:
+
+    * список книг заданного автора; 
+    * список книг, выпущенных заданным издательством;
+    * список книг, выпущенных после заданного года.
+
+5. **Worker**: Фамилия и инициалы, Должность, Год поступления на работу, Зарплата. Данные загрузить из CSV-файла. Вывести:
+
+    * список работников, стаж работы которых на данном предприятии превышает заданное число лет; 
+    * список работников, зарплата которых больше заданной;
+    * список работников, занимающих заданную должность.
+
+6. **Train**: Пункт назначения, Номер поезда, Время отправления, Число общих мест, Купейных, Плацкартных. Данные загрузить из JSON-файла. Вывести:
+
+    * список поездов, следующих до заданного пункта назначения; 
+    * список поездов, следующих до заданного пункта назначения и отправляющихся после заданного часа;
+    * список поездов, отправляющихся до заданного пункта назначения и имеющих общие места.
+
+7. **Product**: Наименование, Производитель, Цена, Срок хранения, Количество. Данные загрузить из JSON-файла. Вывести:
+
+    * список товаров для заданного наименования; 
+    * список товаров для заданного наименования, цена которых не превышает указанной;
+    * список товаров, срок хранения которых больше заданного.
+
+8. **Patient**: Фамилия, Имя, Отчество, Адрес, Номер медицинской карты, Диагноз. Данные загрузить из JSON-файла. Вывести:
+
+    * список пациентов, имеющих данный диагноз; 
+    * список пациентов, номер медицинской карты которых находится в заданном интервале.
+
+9. **Bus**: Фамилия и инициалы водителя, Номер автобуса, Номер маршрута, Марка, Год начала эксплуатации, Пробег. Данные загрузить из JSON-файла. Вывести:
+
+    * список автобусов для заданного номера маршрута; 
+    * список автобусов, которые эксплуатируются больше 10 лет;
+    * список автобусов, пробег у которых больше 10 000 км.
+
+10. **Customer**: Фамилия, Имя, Отчество, Адрес, Телефон, Номер кредитной карточки, Номер банковского счета. Данные загрузить из JSON-файла. Вывести:
+
+    * список покупателей в алфавитном порядке; 
+    * список покупателей, номер кредитной карточки которых находится в заданном интервале.
+
+11. **Процедурный кабинет**: забор различных анализов
+
+12. **Автосалон**
+

+ 183 - 0
articles/lab8-oop1.md

@@ -0,0 +1,183 @@
+# Задачи по теме «Объектно-ориентированное программирование»
+
+>В тех задачах, где нужно выполнять операции сложения, вычитания и т.п. имеется в виду операции с созданными вами типами данных, например:
+>
+>```cs
+>HexNumber one = new HexNumber("ab10");
+>HexNumber two = new HexNumber("cd05");
+>Console.WriteLine(one + two);
+>```
+>Для реализации операции сложения, как в примере выше, в вашем классе нужно сделать [Перегрузку операторов](https://metanit.com/sharp/tutorial/3.36.php). (На лекциях этого не было, но ничего сложного там нет) 
+
+1. Построить систему классов для описания плоских геометри­ческих фигур: круга, квадрата, прямоугольника. Предусмотреть методы для создания объектов, перемещения на плоскости, из­менения размеров и поворота на заданный угол.
+
+2. Построить описание класса, содержащего информацию о почтовом адресе организации. Предусмотреть возможность раздель­ного изменения составных частей адреса, создания и уничтоже­ния объектов этого класса.
+
+3. Составить описание класса для представления комплексных чисел с возможностью задания вещественной и мнимой частей как числами типов **double**, так и целыми числами. Обеспечить выполнение операций сложения, вычитания и умножения комп­лексных чисел.
+
+4. Составить описание класса для работы с цепными списками строк (строки произвольной длины) с операциями включения в список, удаления из списка элемента с заданным значением дан­ного, удаления всего списка или конца списка, начиная с задан­ного элемента.
+
+5. Составить описание класса для объектов-векторов, задавае­мых координатами концов в трехмерном пространстве. Обеспе­чить операции сложения и вычитания векторов с получением нового вектора (суммы или разности), вычисления скалярного произведения двух векторов, длины вектора, косинуса угла меж­ду векторами.
+
+6. Составить описание класса прямоугольников со сторонами, параллельными осям координат. Предусмотреть возможность пе­ ремещения прямоугольников на плоскости, изменения размеров, построения наименьшего прямоугольника, содержащего два заданных прямоугольника, и прямоугольника, являющегося общей частью (пересечением) двух прямоугольников.
+
+7. Составить описание класса для определения одномерных мас­сивов целых чисел (векторов). Предусмотреть возможность обра­щения к отдельному элементу массива с контролем выхода за пределы индексов, возможность задания произвольных границ индексов при создании объекта и выполнения операций поэле­ментного сложения и вычитания массивов с одинаковыми грани­цами индексов, умножения и деления всех элементов массива на скаляр, печати (вывода на экран) элементов массива по индексам и всего массива.
+
+8. Составить описание класса для определения одномерных массивов строк фиксированной длины. Предусмотреть возмож­ность обращения к отдельным строкам массива по индексам, контроль выхода за пределы индексов, выполнения операций поэлементного сцепления двух массивов с образованием ново­го массива, слияния двух массивов с исключением повторяю­щихся элементов, печать (вывод на экран) элементов массива и всего массива.
+
+9. Составить описание класса многочленов от одной перемен­ной, задаваемых степенью многочлена и массивом коэффициен­тов. Предусмотреть методы для вычисления значения многочлена для заданного аргумента, операции сложения, вычитания и ум­ножения многочленов с получением нового объекта-многочлена, печать (вывод на экран) описания многочлена.
+
+10. Составить описание класса одномерных массивов строк, каждая строка которых задается длиной и указателем на выделен­ную для нее память. Предусмотреть возможность обращения к от­дельным строкам массива по индексам, контроль выхода за пре­делы индексов, выполнения операций поэлементного сцепления двух массивов с образованием нового массива, слияния двух мас­сивов с исключением повторяющихся элементов, печать (вывод на экран) элементов массива и всего массива.
+
+11. Составить описание объектного типа **TMatrix**, обеспечиваю­щего размещение матрицы произвольного размера с возможнос­тью изменения числа строк и столбцов, вывода на экран подмат­рицы любого размера и всей матрицы.
+
+12. Простые и иерархические меню.
+
+    1. Спроектировать простое меню в одной строке экрана. Меню обеспечивает перебор пунктов в результате нажатия на клавишу Пробел, позволяет зафиксировать выбор нажатием на клавишу Enter или отказаться от выбора нажатием на клавишу Esc. После выбора одного из пунктов в программу возвращается какое-то значение, связанное с выбранным пунктом, например символ. При отказе от выбора в программу возвращается #27.
+
+        Перед началом работы меню ему надо передать названия пун­ктов и возвращаемые символы (ими могут быть первые буквы пун­ктов или какие-то специальные символы). Все это можно сделать в форме строки вида «Первое Второе Третье» или «Первое (а) Второе (о) Третье (с)» (здесь за названием пункта следует в скобках возвращаемый символ).
+
+        Состояние меню характеризуется координатами меню на экра­не, номером отмеченного пункта, общим количеством пунктов, перечнем названий пунктов и возвращаемых символов (во втором варианте представления).
+
+        Методами объекта являются:
+
+        - _init_ — заполняет поле названий пунктов, подсчитывает ко­личество пунктов, делает выбранным первый пункт;
+        - _select_ — позволяет выбрать пункт меню и возвращает символ выбранного пункта, при отказе от выбора возвращает #27;
+        - _draw_ — рисует меню, выделяя выбранный пункт цветом;
+        - _leftBoard_ — возвращает начало названия данного пункта;
+        - _len_ — возвращает длину названия пункта;
+        - _whatSel_ — возвращает символ выбранного пункта.
+
+    1. Создать новый объект **TNeatMenu**, наследующий **TMenu**, ко­торый, в отличие от своего предка, будет восстанавливать вид экра­на. Для этого нужно добавить новое поле _store_, где будет храниться прежний экран во время действия меню, перекрыть метод _init_ и добавить метод _done_, который восстанавливает состояние экрана.
+
+    1. Создать меню, которое изображает себя в форме столбца. Для этого рационально воспользоваться виртуальными методами. Достаточно изменить метод _draw_ объекта **TNeatMenu** и объявить одноименные методы виртуальными.
+
+    1. Разместить объекты в динамической памяти, для этого дос­таточно описать указатели на них.
+
+    1. Построить сложное иерархическое меню: пробел будет от­крывать главное меню, последовательное нажатие на клавиши **Enter** и **Пробел** будет разворачивать подсвеченный пункт в под­меню или, если пункт находится на нижнем уровне, клавиша **Enter** будет сворачивать подменю. Нажатие на клавишу Esc закан­ чивает работу программы.
+
+    1. Построить иерархическое меню: **пробел** будет открывать глав­ное меню, нажатие на клавишу **Enter** будет разворачивать подсве­ченный пункт в меню или, если пункт находится на самом нижнем уровне, клавиша **Enter** сворачивает подменю. Нажатие на клавишу **Esc** заканчивает работу программы. Нижний уровень — вертикаль­ный.
+
+13. Составить программу, работающую со связанными списками. Мы будем рассматривать связанный список как объект, содер­жащий связанный список данных и операций (методов), которые вы можете с ними выполнять.
+
+    Связанный список данных состоит из указателей на начало («голову») и конец («хвост») связанного списка (в нашем примере из-за его гибкости используется дву­ направленный связанный список). Каждый элемент связанного списка представляет собой реализацию отдельного объекта. Воз­можности, необходимые для использования связанного списка, предоставляют следующие операции:
+
+    * инициализация связанного списка;
+    * деинициализация связанного списка;
+    * вставка элемента в середину списка перед существующим элементом;
+    * присоединение элемента к концу связанного списка;
+    * удаление элемента из связанного списка;
+    * возвращение первого элемента связанного списка;
+    * возвращение последнего элемента связанного списка.
+
+    Вы можете видеть, как объект связанного списка наследуется объектами стека или очереди, по­скольку очередь и стек можно реализовать как связанный список с ограниченным числом операций. Например, можно реализовать очередь в виде связанного списка, в котором элементы могут до­бавляться к концу и извлекаться из начала. Если вы таким обра­зом реализуете очередь, то нужно запретить наследуемые методы связанного списка, которые для очереди недопустимы (напри­мер, вставку в середину списка).
+
+14. Определить объект **TFish** — аквариумная рыбка. Рыбка имеет координаты, скорость, размер, цвет, направление движения. Ме­тодами объекта являются:
+
+    * _init_ — устанавливает значения полей объекта и рисует рыбу на экране методом _draw_.
+    * _draw_ — рисует рыбу в виде уголка с острием в точке _coord_ и направленного острием по ходу движения рыбы.
+    * _look_ — проверяет несколько точек на линии движения рыбы. Если хоть одна из них отличается по цвету от воды, возвращаются ее цвет и расстояние до рыбы.
+    * _run_ — перемещает рыбу в текущем направлении на рассто­яние, зависящее от текущей скорости рыбы. Иногда случайным образом меняет направление движения рыбы. Если рыба видит препятствие, направление движения меняется, пока препятствие не исчезнет из поля зрения рыбы.
+
+15. Определить объект **TAquarium**, который является местом обитания рыб (см. задачу 14 данного раздела). Он представляет со­бой область экрана, наполненную водой. Рыбы живут в аквариу­ме, поэтому экземпляры объекта **TFish** должны быть полями объекта **TAquarium**.
+
+    Методы:
+
+    * _init_ — включает графический режим, заполняет аквариум водой, камнями и рыбами.
+    * _run_ — организует бесконечный цикл, в котором выполняет­ ся метод _run_ всех обитателей аквариума.
+    * _done_ — выключает графический режим.
+
+16. Определить два объекта **TPike** и **ТКагр**, которые наследуют объект **TFish** (см. задачу 14). Оба они отличаются от **TFish** тем, что по-разному изображают себя на экране: **TPike** — в виде зеленой стрелки, а **ТКагр** — в виде красного треугольника. Воспользуйтесь виртуальными методами. Для этого вернитесь к определению **TFish** и откорректируйте его, сделав метод _draw_ пустым и виртуальным.
+
+17. Объединить карпов и щук (см. задачу 16) в две стаи. Стая — это связанный список рыб. Для связи до­бавьте в объекты **TPike** и **ТКагр** поле _next_ — указатель на следу­ющую рыбу в стае (связанный список). Сделайте аквариум владельцем не отдельных рыб, а двух стай и позвольте пользователю пополнять стаи, вводя рыб с клавиатуры.
+
+18. Позволить щукам (см. задачу 16) проявить свой дурной ха­рактер и поедать карпов, как только они их увидят. Здесь возник­нет проблема — установить, какого именно карпа видит щука. Она решается путем просмотра всей стаи карпов и поиска того, чьи координаты близки к координатам данной шуки. Найденный карп удаляется из стаи.
+
+19. Составить программу для игры в шашки. Шашка каждого нового цвета выступает в качестве отдельного объекта. Характеристики шашки — цвет и позиция на доске. Методы — перемещение. Не забудьте о таких объектах, как «дамки».
+
+20. Составить программу для игры в домино. В качестве объектов выступают кости домино. Методы — способы выставления той или иной кости.
+
+21. Составить программу для игры в шахматы. Каждая уникаль­ ная шахматная фигура выступает в качестве отдельного объекта. Она характеризуется цветом, положением на доске, способом перемещения. Предусмотреть возможность превращения пешки в ферзя.
+
+22. Реализовать в виде класса набор методов для выполне­ния следующих операций над комплексными числами:
+
+    - сложения;
+    - вычитания;
+    - умножения;
+    - деления;
+    - модуля комплексного числа;
+    - возведения комплексного числа в степень п (п — натураль­ное).
+
+23. Реализовать в виде класса набор методов для выполнения следующих операций над обыкновенными дробями вида ^ (Р — целое, Q — натуральное):
+
+    - сложения;
+    - вычитания;
+    - умножения;
+    - деления;
+    - сокращения дроби;
+    - возведения дроби в степень п (п — натуральное);
+    - функций, реализующих операции отношения (равно, неравно, больше или равно, меньше или равно, больше, меньше).
+
+24. Реализовать в виде класса набор методов для выпол­нения следующих операций с квадратными матрицами:
+
+    - сложения двух матриц;
+    - умножения одной матрицы на другую;
+    - нахождения транспонированной матрицы;
+    - вычисления определителя матрицы.
+
+25. Реализовать в виде класса набор методов для выпол­нения следующих операций над векторами:
+
+    - сложения;
+    - вычитания;
+    - скалярнго умножения векторов;
+    - умножения вектора на число;
+    - нахождения длины вектора.
+
+26. Реализовать в класс для выполне­ния следующих операций над натуральными числами в `Р`-ичной системе счисления (`2 < Р < 9`):
+
+    - сложения;
+    - вычитания;
+    - умножения;
+    - деления;
+    - перевода из десятичной системы счисления в Р-ичную;
+    - перевода из Р-ичной системы счисления в десятичную;
+    - функции проверки правильности записи числа в Р-ичной системе счисления;
+    - функций, реализующих операции отношения (равно, неравно, больше или равно, меньше или равно, больше, меньше).
+
+27. Реализовать класс для выпол­нения следующих операций над натуральными числами в шест­надцатеричной системе счисления:
+
+    - сложения;
+    - вычитания;
+    - умножения;
+    - деления;
+    - перевода из двоичной системы счисления в шестнадцатеричную;
+    - перевода из шестнадцатеричной системы счисления в деся­тичную;
+    - функции проверки правильности записи числа в шестнад­цатеричной системе счисления;
+    - функций, реализующих операции отношения (равно, неравно, больше или равно, меньше или равно, больше, меньше).
+
+28. Реализовать класс для выпол­нения следующих операций над длинными числами:
+
+    - сложения;
+    - вычитания;
+    - умножения;
+    - нахождения частного и остатка от деления одного числа на Другое;
+    - функций, реализующих операции отношения (равно, неравно, больше или равно, меньше или равно, больше, меньше).
+
+29. Реализовать класс для выпол­нения операций с многочленами от одной переменной (первый многочлен степени `т`, второй — степени `п`):
+
+    - сложения;
+    - вычитания;
+    - умножения;
+    - деления с остатком;
+    - операций отношения (равно, не равно);
+    - возведения в натуральную степень `к`;
+    - вычисления производной от многочлена;
+    - вычисления значения в точке `х0`.
+
+30. Разработать способ представления множеств, содержащих более 255 элементов (до 2000). Создать модуль, позволяющий вы­ полнять следующие операции над элементами таких множеств:
+
+    - объединение;
+    - пересечение;
+    - разность;
+    - функция проверки принадлежности элемента множеству;
+    - функция проверки, является ли данное множество подмно­жеством (надмножеством) другого.

+ 52 - 0
articles/lab8-oop2.md

@@ -0,0 +1,52 @@
+# ООП. Основы (CSV)
+
+На чемпионате **WorldSkills** часто возникает ситуация, когда нужно загрузить данные в базу программно, поэтому нужно уметь делать это быстро и просто. Подробнее мы остановимся на этом курсе "Проектирование и разработка ИС", но тренироваться использовать загрузку данных из CSV будем на лабораторных работах и в этом курсе.
+
+Файл с данными нужно положить в каталог `bin/debug` вашего проекта, либо указать полный путь к нему
+
+```csv
+Акесов,18
+Андреев,19
+```
+
+```cs
+class Peoples{
+    string Name;
+    int Age;
+}
+
+...
+
+var Peoples = new List<People>();
+
+// считываем данные из файла (файл лежит рядышком с .exe)
+string[] data = File.ReadAllLines("ваш файл.csv");
+
+// проходим в цикле по считанным строкам
+foreach (var line in data)
+{
+    // разделяем каждую строку на массив строк с указанием символа разделителя
+    var values = line.Split(',');
+
+    Peoples.Add( new People {
+        Name=values[0],
+        Age=Convert.ToInt32(values[1])
+    });
+}
+```
+
+## Примечания
+
+1. Дату в CSV желательно хранить в формате `YYYY-MM-DD` (SQL-формат). Такой формат нормально преобразуется в класс **Data** (**DataTime**) штатным конвертером. Иначе придется разбирать строку даты на составные части (функцией _split_) и создавать дату из этих частей (`new Date(year, month, day)`)
+
+2. В качестве разделителя дробной части в числах с плавающей запятой (**float**, **double**) в CSV обычно используется точка. Но конвертер использует настройки локальной среды, а для России в качестве разделителя задана запятая, поэтому конвертер не может разобрать строку с точкой и выбрасывает исключение. В этом случае можно либо заменить точки на запятые в CSV файле (но в таком случае и разделителем полей должен быть другой знак), либо в конвертер передать дополнительный параметр, явно указывающий разделитель для дробной части:
+
+```cs
+Convert.ToDouble("113.12", new NumberFormatInfo(){NumberDecimalSeparator="."});
+```
+
+# Задача
+
+* сформировать файл с данными в формате CSV для вашей предметной области
+* загрузить данные с программу из подготовленного файла
+

+ 55 - 0
articles/lab8-oop3.md

@@ -0,0 +1,55 @@
+# Лабораторная работа. ООП. Наследование. Абстрактные классы.
+
+Варианты те же что и на прошлой лабораторной.
+
+Данные загружать из файлов CSV, XML и JSON (объекты каждого класса из своего формата).
+
+1. Создать абстрактный класс **Транспортное средство** и производные классы **Автомобиль**, **Велосипед**, **Повозка**. Подсчитать время и стоимость перевозки пассажиров и грузов каждым транспортным средством.
+
+2. Создать абстрактный класс **Грузоперевозчик** и производные классы **Самолет**, **Поезд**, **Автомобиль**. Определить время и стоимость перевозки для указанного расстояния.
+
+3. Создать абстрактный класс **Пассажироперевозчик** и производные классы **Самолет**, **Поезд**, **Автомобиль**. Определить время и стоимость передвижения.
+
+4. Создать абстрактный класс **Учащийся** и производные классы **Школьник**, **Студент**, **Аспирант**. Показать отдельно студентов и школьников.
+
+5. Создать абстрактный класс **Музыкальный инструмент** и производные классы **Ударный**, **Струнный**, **Духовой**. Создать массив объектов **Оркестр**. Выдать состав оркестра, переопределив метод.
+
+6. Создать абстрактный класс **Работник фирмы** и производные классы **Менеджер**, **Администратор**, **Программист**. Сформировать зарплатную ведомость с учетом надбавки за стаж.
+
+7. Создать абстрактный класс **Домашнее животное** и производные классы **Собака**, **Кошка**, **Попугай** и др. С помощью конструктора установить имя каждого животного и его характеристики. Сформировать график прививок.
+
+8. Создать абстрактный класс **Садовое дерево** и производные классы **Яблоня**, **Вишня**, **Груша** и др. С помощью конструктора автоматически установить номер каждого дерева. Принять решение о пересадке каждого дерева в зависимости от возраста и плодоношения.
+
+9. Создать абстрактный класс **Средство доставки** и производные классы: **Вертолёт**, **Кукурузник**, **Реактивный самолет**. Расчитать стоимость и время доставки РДГ.
+
+10. Создать абстрактный класс **Блюдо** и производные классы: **Суп**, **Каша**, **Компот**. Рассчитать время приготовления комплексного обеда из трех блюд и количество ККал.
+
+<!-- 
+
+Студент, преподаватель, персона, заведующий кафедрой
+Служащий, персона, рабочий, инженер
+Рабочий, кадры, инженер, администрация
+Деталь, механизм, изделие, узел
+Организация, страховая компания, нефтегазовая компания, завод
+Журнал, книга, печатное издание, учебник
+Тест, экзамен, выпускной экзамен, испытание
+Место, область, город, мегаполис
+Игрушка, продукт, товар, молочный продукт
+Квитанция, накладная, документ, счет
+Автомобиль, поезд, транспортное средство, экспресс
+Двигатель, дизель, двигатели внутреннего сгорания и реактивный
+Республика, монархия, королевство, государство
+Млекопитающее, парнокопытное, птица, животное
+Товар, велосипед, горный велосипед, самокат
+Лев, дельфин, птица, синица, животное
+Музыкант, персона, студент, гитарист
+Печатное издание, газета, книга, периодика
+Корабль, пароход, парусник, корвет
+Стихотворение, стиль изложения, рифма, проза
+Поселок, область, район, город
+Грузовик, автомобиль, легковое авто, транспорт
+Спорт, футбол, хобби, музыка
+Молоток, инструмент, гитара, звук
+Окружность, геометрическая фигура, линия, заливка
+
+ -->

+ 76 - 0
articles/lab8-oop4.md

@@ -0,0 +1,76 @@
+# ООП. Интерфейсы.
+
+Как мы помним из лекций, интерфейсы позволяют уйти от сильного связывания кода. Т.е. вместо использования переменной конкретного класса (если вспомнить трансформеров, то вместо класса "винтовка" мы используем интерфейс "оружие") мы используем переменную типа интерфейс, которой может быть присвоен любой объект, который реализует этот интерфейс.
+
+```cs
+// объявляем интерфейс - это просто список методов, который должен реализовать класс
+interface IWeapon
+{
+    void Fire();
+}
+
+// класс, реализующий интерфейс
+class Berdanka : IWeapon
+{
+    public override void Fire()
+    {
+        Console.WriteLine("Ба-бах");
+    }
+}
+
+// еще один класс, реализующий интерфейс
+class Палка : IWeapon
+{
+    public override void Fire()
+    {
+        if(прошёл_год)
+            Console.WriteLine("Раз в год и палка стреляет!");
+        else
+            Console.WriteLine("Я просто палка");
+    }
+}
+
+class Transformer
+{
+    // у трансформера есть свойство СуперОружие типа IWeapon (т.е. может стрелять)
+    IWeapon SuperWeapon;
+
+    public Transformer()
+    {
+        // на складе выдали берданку
+        SuperWeapon = new Berdanka();
+    }
+
+    public void Attack()
+    {
+        SuperWeapon.Fire();
+    }
+}
+```
+
+Т.е. трансформеру пофиг что ему всучили на складе, главное что это реализует интерфейс **IWeapon** (умеет стрелять).
+
+Интерфейсы часто используются при интеграционном тестировании в качестве заглушек. Например, в приложении есть класс API, который реализует авторизацию на сервере (login, logout...). Хорошей практикой является вынос этих методов в интерфейс и реализация при тестировании класса заглушки:
+
+```cs
+class Mock: IAPI
+{
+    public bool Login(string name, string password)
+    {
+        return true;
+    }
+    public bool Logout()
+    {
+        return true;
+    }
+}
+```
+
+Т.е. при тестировании приложения мы не зависим от наличия сети и работоспособности сервера, и проверяем только работоспособность приложения.
+
+**Задания**
+
+1. АПИ: логин, логаут
+2. Отчет: вывод на экран, сохранение в файл, печать
+3. Трансформер, атака: лазер, ракета, пулемет
+4. Доступ к даным: БД, XML, массив

+ 4 - 0
articles/lab_class_lib.md

@@ -0,0 +1,4 @@
+# Лабораторная работа "Создание библиотеки классов"
+
+1. Перенести интерфейс **IDataProvider**, реализованный в лабораторных работах темы №8 в библиотеку классов.
+1. В основном проекте реализуйте статическое подключение реализованной библиотеки классов.

+ 62 - 0
articles/lab_debug.md

@@ -0,0 +1,62 @@
+# Отладка приложения
+
+При написании кода неизбежно возникают ошибки и если синтаксические ошибки нам помогает обнаружить среда разработки (IDE), то часть ошибок могут проявиться только при работе приложения (**Run Time Error** - ошибки времени выполнения). Это могут быть как логические ошибки программиста, так и неверные входные данные.
+
+Поиск ошибок выполняется двумя способами: *отладка* и *тестирование*. Тестирование отдельная большая тема и мы будем изучать его в следующем году.
+
+Все IDE позволяют производить отладку приложения, т.е. пошаговое выполнение кода с просмотром значений переменных (для компилируемых приложений есть специальный режим сборки - **debug**, когда в код программы включается отладочная информация, позволяющая связать двоичный код с исходным). 
+
+Перед началом отладки нужно убедиться, что в IDE установлен режим сборки **Debug**:
+
+![](../img/debug_01.png)
+
+Перед запуском в режиме отладки установите **точку остановки** в том месте кода, которое вы хотите проверить (просто кликнув кнопкой по левому полю):
+
+![](../img/debug_02.png)
+
+Когда выполнение приложения дойдет до этой точки, выполнение приостановится и мы сможем исследовать состояние и пошагово выполнить код:
+
+* желтая стрелка слева показывает в каком месте кода мы находимся
+* в нижней панели есть кнопки, позволяющие выполнять код по шагам. эти кнопки имеют и быстрые клавиши
+
+    * *шаг с заходом* (F7) - используется, если мы хотим "зайти" в функцию, которая находится в текущей строке
+    * *шаг с обходом* (F8) - выражение в текущей строке выполняется за один шаг, без захода
+
+* значение переменной можно посмотреть 
+    * либо просто наведя курсор на неё:
+
+        ![](../img/debug_04.png)
+
+        Если переменная не скалярного типа, а объект, то содержимое объекта можно посмотреть кликнув по треугольнику слева от содержимого:
+
+        ![](../img/debug_05.png)
+
+    * либо, если нам нужно отслеживать её значение на каждом шаге, внизу экрана 
+
+        ![](../img/debug_06.png)
+
+        Можно на вкладке "контрольные значения" добавить сложное выражение (цепочку свойств объекта+- или даже вызов функции)
+
+        ![](../img/debug_07.png)
+
+## Задание
+
+Выполните в режиме отладки любую программу из прошлой лабораторной работы. Все этапы сохраняйте скриншотами и включите их в отчёт (`readme.md`)
+
+1. Используйте точки останова
+1. Отслеживайте значения переменных на вкладке "контрольные значения"
+
+Пример включения изображения в `md` файл
+
+```md
+Формат
+
+![alt-строка (не обязательно)](<путь к изображению>)
+
+Пример из этой лекции
+
+![](../img/debug_07.png)
+```
+
+* Имена файлов чувствительны к регистру.
+* Используйте относительные пути (например, для текущего каталога `./`)

+ 67 - 0
articles/lab_truth_table.md

@@ -0,0 +1,67 @@
+# Лабораторная работа: ветвление в репозитории; таблицы истинности.
+
+## Ветвление в репозитории
+
+Чтобы не плодить репозитории для лабораторных работ, будем все их делать в одном репозитории.
+
+1. Заходим в каталог с репозитрием (создаём новый, если ещё нет)
+
+1. Выполняем команду `git status`
+
+    В ответе смотрим название ветки и есть ли несохраненные файлы (напоминаю, что сменить ветку можно только если в репозитории нет несохраненных файлов)
+
+    ```
+    Текущая ветка: master
+                   ^^^^^^
+    ```
+
+1. Создаём новую ветку и переходим на неё командой `git checkout -b lab_truth_table`
+
+    В ответе, если всё нормально, увидим, что переключились на новую ветку
+
+    ```
+    Переключились на новую ветку «lab_truth_table»
+    ```
+
+1. Удаляем всё из файла `readme.md`
+
+## Заполнение таблиц истинности
+
+Теорию и пример заполнения смотрим в [лекции](./t1l3.md#решение-логических-выражений). Обратите внимание, что внутри таблицы знак "|" (ИЛИ) используется как разделитель столбцов. Чтобы использовать его как обычный занк, нужно использовать экранирование: "\|"
+
+## Задания
+
+Выполните один пример, номер варианта возьмите из порядкового номера в журнале
+
+1. `A | B & C`
+2. `A & B & C`
+3. `A & B | C`
+4. `!A | B & C`
+5. `A | !B & C`
+6. `A | B & !C`
+7. `!A | !B & C`
+8. `A | !B & !C`
+9. `!A | B & !C`
+10. `!A | !B & !C`
+11. `(A | B) & C`
+12. `!(A | B) & C`
+13. `A | !(B & C)`
+14. `!(A | B & C)`
+15. `A ^ B & C`
+16. `A | B ^ C`
+17. `A ^ B ^ C`
+18. `A & B ^ C`
+19. `!A ^ B & C`
+20. `A ^ !B & C`
+21. `A ^ B & !C`
+22. `!A | B ^ C`
+23. `A | !B ^ C`
+24. `A | B ^ !C`
+25. `A ^ B ^ C`
+26. `!A ^ B ^ C`
+27. `A ^ !B ^ C`
+28. `A ^ B ^ !C`
+29. `A ^ B | C`
+30. `!A | B ^ C`
+
+В отчете (`readme.md`) напишите решаемое задание и его решение в виде таблицы истинности. Сохраните репозиторий и опубликуйте его на сервере kolei.ru (не забывайте, что публикуете другую ветку). Скиньте ссылку на ветку в репозитории в группу.

+ 22 - 0
articles/lab_wpf_base.md

@@ -0,0 +1,22 @@
+# Лабораторная работа "Создание WPF-приложения. Знакомство с компоновкой"
+
+1. Создать приложение WPF .NET Framework
+
+2. Исследовать контейнеры 
+    - **Grid**: создать сетку с разными типами выравнивания (auto, фискированное, динамическое)
+    - **StackPanel**: создать панели с разной ориентацией и выравниванием вложенных эелементов 
+    - **WrapPanel**
+
+3. Результаты оформить в формате MarkDown с использованием примеров разметки
+    
+    \`\`\`xml
+    ```xml
+    <StackPanel>
+    ...
+    </StackPanel>
+    ```
+    \`\`\`
+    
+    и приложить скриншоты
+
+    !\[Альтернативное название\]\(путь к изображению\)

+ 3 - 0
articles/lab_wpf_binding.md

@@ -0,0 +1,3 @@
+# Каркас приложения. Модель данных. Привязка данных.
+
+Используя материалы лекции [Каркас приложения. Модель данных. Привязка данных. Табличный вывод.](../articles/wpf_template.md) и классы из предыдущих [лабораторных работ](../articles/lab8-oop.md)  разработать WPF-приложение. Логотип подставить свой, соответствующий вашей предметной области.

+ 99 - 0
articles/lab_wpf_data_csv.md

@@ -0,0 +1,99 @@
+# Получение данных из внешних источников. CSV.
+
+1. Для начала создаём файл с данными для импорта. Я продолжаю тему *кошек*, вы реализуете свою предметную область:
+
+    >Мы должны уметь работать с разными типами данных, поэтому я добавил в исходные данные тип **Date** (дата прививки). Формат даты имеет множество разных форматов, но при экспорте обычно используют SQL формат (`yyyy-mm-dd`)
+
+    ```csv
+    age,breed,color,name,photo,dateOfLastVaccination
+    1,"Дворняжка","Белый","Ириска",,2024-04-29
+    2,"Шотландская вислоухая","Коричневый","Изи",,2020-01-31
+    3,"Сиамский","Цветной","Макс",,2022-05-10
+    ```
+
+    * в первой строке названия полей, соответствующие модели данных
+    * разделитель запятая
+    * строковые литералы (текст, в котором могут встретиться спец.символы, а лучше все текстовые поля) заключаем в двойные кавычки
+    * для отсутствующих полей (у меня `photo`) оставляем пустую строку
+
+    Файл сохраняем в подкаталог `bin/debug/net8.0-windows` вашего проекта - туда, где создается исполняемый (.exe) файл (название зависит от версии .NET и платформы).
+
+1. Правим модель (у нас добавилось свойство "Дата последней прививки")
+
+    ```cs
+    public class Cat
+    {
+        public string name { get; set; }
+        public int age{ get; set; }
+        public string color { get; set; }
+        // порода
+        public string breed { get; set; }
+        public string photo { get; set; }
+        // новое свойство
+        public DateOnly dateOfLastVaccination { get; set; }  
+    }
+    ```
+
+1. Загружаем данные из файла в класс поставщика данных
+
+    >Тут есть два варианта: либо каждый раз загружать данные при вызове метода **getCats**, либо загрузить один раз при создании экземпляра класса (в конструкторе). Оба варианта имеют право на жизнь: первый имеет смысл применять, если данные часто меняются, второй, если данные статичны. Я реализую второй вариант.
+
+    [Вспоминаем материалы лекции про типы данных](./t5_file_types.md#csv) и создаём для работы с CSV файлами класс **CSVDataProvider**, реализующий интерфейс **IDataProvider**:
+
+    * Создаём приватную переменную для хранения загруженного списка и считываем данные в конструкторе:
+
+        Библиотека **CsvHelper** достаточно "умная" и преобразует строку в дату автоматически
+
+        Но в то же время они и "слишком умная", если данные не соответсвуют модели (не хватает полей) или типу (например, в дате нет времени), то запись игнорируется
+
+        ```cs
+        public class SCVDataProvider : IDataProvider
+        {
+            private List<Cat> catList;
+
+            // конструктор класса
+            public CSVDataProvider()
+            {
+                using (var reader = new StreamReader("./cat.csv")) {
+                    using (var csv = new CsvReader(
+                        reader, 
+                        CultureInfo.InvariantCulture))
+                    {
+                        // CsvHelper использует отложенное чтение через 
+                        // yeld, поэтому сразу преобразуем в список
+                        catList = csv.GetRecords<Cat>().ToList();
+                    }
+                }
+            }
+        }
+        ```
+
+    * реализуем интерфейс **IDataProvider** (пишем метод **getCats**):
+
+        ```cs
+        public IEnumerable<Cat> getCats()
+        {
+            return catList;
+        }
+        ```
+
+1. Теперь в конструкторе окна меняем класс провайдера и всё продолжает работать
+
+    ```cs
+    public MainWindow()
+    {
+        InitializeComponent();
+        DataContext = this;
+        // Globals.dataProvider = new LocalDataProvider();
+        Globals.dataProvider = new CSVDataProvider();
+        CatList = Globals.dataProvider.getCats();
+    }
+    ```
+
+# ЗАДАНИЕ
+
+1. Подготовить набор данных (не менее 10 записей) с разными типами (обязательно должны быть: Int, Double, String, DateTime, Boolean)
+
+1. Реализовать класс CSVDataProvider для своей предметной области
+
+1. Поле с датой добавить в таблицу и указать формат вывода: `Binding="{Binding YourColumn,StringFormat='dd.MM.yyyy'}"`

+ 133 - 0
articles/lab_wpf_data_json.md

@@ -0,0 +1,133 @@
+# Получение данных из внешних источников. JSON.
+
+[Вспоминаем материалы лекции про типы данных](./t5_file_types.md#json)
+
+1. Подгатавливаем файл с исходными данными:
+
+    ```json
+    [
+        {
+            "age": 1,
+            "breed": "Дворняжка",
+            "color": "Белый",
+            "name": "Ириска",
+            "dateOfLastVaccination": null
+        },
+        {
+            "age": 2,
+            "breed": "Шотландская вислоухая",
+            "color": "Коричневый",
+            "name": "Изи",
+            "dateOfLastVaccination": "2020-01-31"
+        },
+        {
+            "age": 3,
+            "breed": "Сиамский",
+            "color": "Цветной",
+            "name": "Макс",
+            "dateOfLastVaccination": "2022-05-10"
+        },
+    ]
+    ```
+
+    * `[]` - квадратные скобки означают, что у нас массив
+    * `{}` - в фигурных скобках записана информация об объекте
+    * названия полей (*age*, *bread* и т.д.) пишутся в кавычках
+    * значения полей записываются в зависимости от типа данных
+        * **int** - просто число
+        * **double** - число с разделителем точка
+        * **boolean** - литералы **true** или **false**
+        * **string** - в кавычках, если в тексте есть кавычки, то они экранируются: `\"` 
+        * **date** - для даты отдельного формата нет, записывается как строка
+
+1. Импорт данных
+
+    Для поддержки работы десериализатора и для обхода проблемы с датой в виде строки будем использовать [первый](./t5_file_types.md#работа-с-json-попытка-№2-datacontractjsonserializer) вариант десериализатора. 
+
+    Добавляем в класс аттрибуты *[DataContract]* и *[DataMember]*:
+
+    ```cs
+    [DataContract]
+    public class Cat
+    {
+        [DataMember]
+        public string name { get; set; }
+        [DataMember]
+        public int age{ get; set; }
+        [DataMember]
+        public string color { get; set; }
+        [DataMember]
+        public string breed { get; set; }
+        [DataMember]
+        public string photo { get; set; }
+
+        [DataMember(Name = "dateOfLastVaccination")]
+        private string? stringDate { get; set; }
+
+        [IgnoreDataMember]
+        public DateTime? dateOfLastVaccination {
+            get
+            {
+                return stringDate == null ? null : DateTime.Parse(stringDate);  
+            }
+            set
+            {
+                stringDate = value.ToString();
+            } 
+        }
+    }
+    ```
+
+    И реализуем класс JSONDataProvider:
+
+    ```cs
+    public class JSONDataProvider
+    {
+        private List<Cat> _catList;
+        
+        public JSONDataProvider()
+        {
+            var serializer = new DataContractJsonSerializer(typeof(Cat[]));
+            using (var sr = new StreamReader("/home/kei/temp/test.json"))
+            {
+                _catList = ((Cat[])serializer.ReadObject(sr.BaseStream)).ToList();
+            }
+        }
+
+        public IEnumerable<Cat> getCats()
+        {
+            return _catList;
+        }
+    }
+    ```
+
+1. В классе окна меняем поставщика данных
+
+# ЗАДАНИЕ
+
+1. Подготовить набор данных (не менее 10 записей) с разными типами (обязательно должны быть: Int, Double, String, DateTime, Boolean)
+
+1. Реализовать класс JSONDataProvider для своей предметной области
+
+1. Поле с датой добавить в таблицу и указать формат вывода: `Binding="{Binding dateOfLastVaccination,StringFormat='dd.MM.yyyy'}"`
+
+---
+
+>Если вдруг нам приспичит делать десериализацию используя компонент **Newtonsoft.Json**, то для обхода проблемы со строковой датой можно реализовать класс-наследник **CatWithStringDates**, в котором перегрузить поле *dateOfLastVaccination*:
+>```cs
+>public class CatWithStringDates: Cat
+>{
+>    public string dateOfLastVaccination { get; set; }
+>}
+>```
+>И при десериализации использовать этот класс:
+>```cs
+>var catList = JsonConvert.DeserializeObject<CatWithStringDates[]>("[{\"age\": 1, \"dateOfLastVaccination\": \"2020-01-01\"}]");
+>```
+>Затем, используя LINQ запрос преобразовать к нужному типу:
+>```cs
+>_catList = catList.Select(cat => new Cat {
+>   age = cat.age,
+>   dateOfLastVaccination = DateTime.Parse(cat.dateOfLastVaccination)
+>}).ToList();
+>```

+ 3 - 0
articles/lab_wpf_elements.md

@@ -0,0 +1,3 @@
+# Исследование визуальных компонентов WPF.
+
+Используя материалы лекций [Элементы управления. Обзор.](./t8_elements.md.md) исследовать визуальные элементы WPF. В отчет приложить скриншоты.

+ 3 - 0
articles/lab_wpf_filter.md

@@ -0,0 +1,3 @@
+# Фильтрация списка
+
+Используя материалы [лекции](./wpf_filtering.md) добавьте в своё приложение фильтрацию по словарному полю ([первый вариант](./wpf_filtering.md#фильтрация-по-словарю)) **и** по условию ([второй вариант](./wpf_filtering.md#фильтрация-по-условию)). То есть реализуйте **оба** варианта фильтрации.

+ 3 - 0
articles/lab_wpf_search_sort.md

@@ -0,0 +1,3 @@
+# Поиск/сортировка
+
+Используя материалы [лекции](./wpf_search_sort.md) добавьте в своё приложение поиск **и** сортировку.

+ 174 - 0
articles/network_json.md

@@ -0,0 +1,174 @@
+[содержание](/readme.md)  
+
+# Http запросы
+
+В Kotlin-е есть встроенные функции работы с http-запросами, но стандартный код для сетевых запросов сложен, излишен и в реальном мире почти не используется. Используются библиотеки. Самые популярные: OkHttp, Fuel и Retrofit.
+
+Мы рассмотрим работу с http на примере библиотеки [Fuel](https://github.com/kittinunf/fuel).
+
+## Установка библиотеки
+
+1. В зависимости приложения добавляем (что такое зависимости и куда их добавлять мы рассмотрим в теме про работу с IDE): 
+
+```
+implementation 'com.github.kittinunf.fuel:fuel-android:2.2.1' 
+```
+
+> актуальную версию библиотеки уточняйте на сайте разработчика
+
+2. В секцию импорта добавить ``import com.github.kittinunf.result.Result``.
+
+## Примеры запросов
+
+Запросы бывают двух типов: синхронные (приложение ждет ответа от сервера, останавливая работу) и асинхронные (запрос посылается в фоне, выполнение программы продолжается - такой режим предпочтительнее, т.к. не "замораживает" интерфейс). Но мы с вами уже знаем, что существуют корутины и мы любой синхронный код можем запустить асинхронно, поэтому дальше будем рассматривать синхронный код.
+
+Запрос можно сформировать через фабричные методы класса Fuel:
+
+```kt
+Fuel.get(...)
+Fuel.post(...)
+// полный список доступных методов см. на сайте библиотеки, нам достаточно этих двух
+```
+
+Или через расширение объекта String:
+
+```kt
+"http://адрес.сайта".httpGet()
+"http://адрес.сайта".httpPost()
+```
+
+Далее рассматривается авторизация на тестовом сервисе:
+
+> Вариантов авторизации существует множество, далее рассмотрен вариант, который использовался в демо-экзамене на курсах "Мастера 5000"
+
+**Алгоритм авторизации**:
+
+* при любом запросе (кроме login и logout) должен добавляться параметр token
+если токен не найден, то вернется сообщение, что пользователь не авторизован: 
+  ```json
+  {"notice":{"answer": "user not authorized"}}
+  ```
+
+* для получения токена необходимо послать **post** запрос на URL */login* с параметрами login и password (в качестве логина используйте *ИФамилия* в латинской транскрипции, т.е. Евгений колесников = EKolesnikov. Пароль любой)
+
+* если токен уже был получен, то сервер вернет ошибку 
+  ```json
+  {"notice":{"answer": "User is active"}}
+  ```
+  в этом случае нужно разлогиниться (послать запрос с теми же параметрами на URL */logout*)
+
+* при успешной авторизации сервер вернет токен: 
+  ```json
+  {"notice": {"token":123}
+  ```
+
+> Адрес тестового сервера уточняйте в начале работы
+
+```kt
+import com.github.kittinunf.fuel.Fuel
+import com.github.kittinunf.result.Result
+
+fun main() {
+    // здесь УРЛ другого сервиса, при тестировании авторизации вставляйте свой
+    val URL = "https://httpbin.org"
+    val (_, _, result) = Fuel
+        .post(
+            "$URL/post",                        // путь тоже должен быть другой
+            listOf( "login" to "EKolesnikov",   // в массиве передаются параметры запроса
+                    "password" to "123456"))
+        .responseString()
+
+    // разбираем результат
+    when (result) {
+        is Result.Failure -> {
+            // на ошибку лучше выбросить исключение
+            println( result.getException().toString() )
+        }
+        is Result.Success -> {
+            // тут реализуете разбор полученного ответа
+            println( result.get() )
+        }
+    }
+}
+```
+
+На этот запрос придет примерно такой ответ:
+
+```json
+{
+  "args": {}, 
+  "data": "", 
+  "files": {}, 
+  "form": {
+    "login": "EKolesnikov", 
+    "password": "123456"
+  }, 
+  "headers": {
+    "Accept": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2", 
+    "Content-Length": "33", 
+    "Content-Type": "application/x-www-form-urlencoded", 
+    "Host": "httpbin.org", 
+    "User-Agent": "Java/11.0.5", 
+    "X-Amzn-Trace-Id": "Root=1-5e3d990d-f0d25f92f87cec3612db9f75"
+  }, 
+  "json": null, 
+  "origin": "888.888.888.888", 
+  "url": "https://httpbin.org/post"
+}
+```
+
+Тут, как и в ответе сервиса, данные в формате JSON.
+
+## JSON
+
+JSON (JavaScript Object Notation) - простой формат обмена данными, удобный для чтения и написания как человеком, так и компьютером. JSON - текстовый формат, полностью независимый от языка реализации.
+
+JSON основан на двух структурах данных:
+
+* Коллекция пар ключ/значение. В разных языках, эта концепция реализована как объект, запись, структура, словарь, хэш, именованный список или ассоциативный массив.
+
+  ```json
+  "login":"EKolesnikov"
+  ```
+
+* Упорядоченный список значений. В большинстве языков это реализовано как массив, вектор, список или последовательность.
+
+  ```json
+  [1,2,3]
+  ```
+
+Это универсальные структуры данных. Почти все современные языки программирования поддерживают их в какой-либо форме. Логично предположить, что формат данных, независимый от языка программирования, должен быть основан на этих структурах.
+
+Подробное описание можете посмотреть в интернете, для нас достаточно знание следующих типов:
+
+* *"литерал"* - в двойных кавычках строка в формате UTF-8, строка может быть как ключем, так и значением:
+  ```json
+  "login":"EKolesnikov"
+  ```
+
+* *{объект}* - неупорядоченный набор пар ключ:значение, причем значением может быть любой другой тип. Пары разделяются запятыми:
+  ```json
+  {"login":"EKolesnikov", "value": 1, "object": {...}, "array": [...]}
+  ```
+
+* *[массив]* - упорядоченная коллекция значений. Значения разделены запятой. Значением может быть любой другой тип.
+
+  ```json
+  ["login", 1, {...}, [...], null, true]
+  ```
+
+* *числа* - числа пишутся без кавычек, могут быть как ключем (не уверен - проверить), так и значением. Дробная часть обозначается точкой:
+
+  ```json
+  "int": 123, "float": 12.34
+  ```
+
+* логические константы и null:
+
+  ```json
+  true, false, null
+  ```
+
+Для работы с JSON в котлине есть типы JSON
+
+[содержание](/readme.md)  

+ 314 - 0
articles/regex.md

@@ -0,0 +1,314 @@
+# Регулярные выражения
+
+## Что такое регулярные выражения?
+
+Если вам когда-нибудь приходилось работать с командной строкой, вы, вероятно, использовали маски имён файлов. Например, чтобы удалить все файлы в текущей директории, которые начинаются с буквы "d", можно написать ``rm d*``.
+
+Регулярные выражения представляют собой похожий, но гораздо более сильный инструмент для поиска строк, проверки их на соответствие какому-либо шаблону и другой подобной работы. Англоязычное название этого инструмента — Regular Expressions или просто RegExp. Строго говоря, регулярные выражения — специальный язык для описания шаблонов строк.
+
+Реализация этого инструмента различается в разных языках программирования, хоть и не сильно. В данной статье мы будем ориентироваться в первую очередь на реализацию Perl Compatible Regular Expressions. 
+
+## Основы синтаксиса
+
+В первую очередь стоит заметить, что любая строка сама по себе является регулярным выражением. Так, выражению "Хаха", очевидно, будет соответствовать строка "Хаха" и только она. Регулярные выражения являются регистрозависимыми, поэтому строка "хаха" (с маленькой буквы) уже не будет соответствовать выражению выше.
+
+Однако уже здесь следует быть аккуратным — как и любой язык, регулярные выражения имеют спецсимволы, которые нужно экранировать. Вот их список: ``. ^ $ * + ? { } [ ] \ | ( )``. Экранирование осуществляется обычным способом — добавлением \ перед спецсимволом.
+
+
+## Набор символов
+
+Предположим, мы хотим найти в тексте все междометия, обозначающие смех. Просто "Хаха" нам не подойдёт — ведь под него не попадут "Хехе", "Хохо" и "Хихи". Да и проблему с регистром первой буквы нужно как-то решить.
+
+Здесь нам на помощь придут наборы — вместо указания конкретного символа, мы можем записать целый список, и если в исследуемой строке на указанном месте будет стоять любой из перечисленных символов, строка будет считаться подходящей. Наборы записываются в квадратных скобках — шаблону [abcd] будет соответствовать любой из символов "a", "b", "c" или "d".
+
+Внутри набора большая часть спецсимволов не нуждается в экранировании, однако использование "\" перед ними не будет считаться ошибкой. По прежнему необходимо экранировать символы "\" и "^", и, желательно, "]" (так, ``[][]`` обозначает любой из символов "]" или "[", тогда как [[]х] – исключительно последовательность "[х]"). Необычное на первый взгляд поведение регулярок с символом "]" на самом деле определяется известными правилами, но гораздо легче просто экранировать этот символ, чем их запоминать. Кроме этого, экранировать нужно символ "-", он используется для задания диапазонов (см. ниже).
+
+Если сразу после "[" записать символ "^", то набор приобретёт обратный смысл — подходящим будет считаться любой символ кроме указанных. Так, шаблону [^xyz] соответствует любой символ, кроме, собственно, "x", "y" или "z".
+
+Итак, применяя данный инструмент к нашему случаю, если мы напишем [Хх][аоие]х[аоие], то каждая из строк "Хаха", "хехе", "хихи" и даже "Хохо" будут соответствовать шаблону.
+
+## Предопределённые классы символов
+
+Для некоторых наборов, которые используются достаточно часто, существуют специальные шаблоны. Так, для описания любого пробельного символа (пробел, табуляция, перенос строки) используется \s, для цифр — \d, для символов латиницы, цифр и подчёркивания "_" — \w.
+
+Если необходимо описать вообще любой символ, для этого используется точка — ".". Если указанные классы написать с заглавной буквы (\S, \D, \W) то они поменяют свой смысл на противоположный — любой непробельный символ, любой символ, который не является цифрой, и любой символ кроме латиницы, цифр или подчёркивания соответственно.
+
+Также с помощью регулярных выражений есть возможность проверить положение строки относительно остального текста. Выражение \b обозначает границу слова, \B — не границу слова, ^ — начало текста, а $ — конец (cледует иметь в виду, что якоря никак не учитывают переводы строк — имеется в виду начало или конец всего текста, а не одной строки в тексте). Так, по шаблону \bJava\b в строке "Java and JavaScript" найдутся первые 4 символа, а по паттерну \bJava\B — символы c 10-го по 13-й (в составе слова "JavaScript").
+
+## Диапазоны
+
+У вас может возникнуть необходимость обозначить набор, в который входят буквы, например, от "б" до "ф". Вместо того, чтобы писать [бвгдежзиклмнопрстуф] можно воспользоваться механизмом диапазонов и написать [б-ф]. Так, паттерну x[0-8A-F][0-8A-F] соответствует строка "xA6", но не соответствует "xb9" (во-первых, из-за того, что в диапазоне указаны только заглавные буквы, во-вторых, из-за того, что 9 не входит в промежуток 0-8).
+
+Механизм диапазонов особенно актуален для русского языка, ведь для него нет конструкции, аналогичной \w. Чтобы обозначить все буквы русского алфавита, можно использовать паттерн [а-яА-ЯёЁ]. Обратите внимание, что буква "ё" не включается в общий диапазон букв, и её нужно указывать отдельно.
+
+## Квантификаторы (указание количества повторений)
+
+Вернёмся к нашему примеру. Что, если в "смеющемся" междометии будет больше одной гласной между буквами “х”, например “Хаахаааа”? Наша старая регулярка уже не сможет нам помочь. Здесь нам придётся воспользоваться квантификаторами.
+
+Квантификатор | Число повторений | Пример | Подходящие строки
+--------------|------------------|--------|------------------
+{n} | Ровно n раз | Ха{3}ха | Хаааха
+{m,n} | От m до n включительно | Ха{2,4}ха | Хааха, Хаааха, Хааааха
+{m,} | Не менее m | Ха{2,}ха | Хааха, Хаааха, Хааааха и т. д.
+{,n} | Не более n | Ха{,3}ха | Хха, Хаха, Хааха, Хаааха
+
+
+Обратите внимание, что квантификатор применяется только к символу, который стоит перед ним.
+
+Некоторые часто используемые конструкции получили в языке регулярных выражений специальные обозначения:
+
+Квантификатор | Аналог | Значение
+:------------:|:------:|---------
+? | {0,1} | Ноль или одно вхождение
+``*`` | {0,} | Ноль или более
+``+`` |	{1,} | Одно или более
+
+
+Таким образом, с помощью квантификаторов мы можем улучшить наш шаблон для междометий до ``[Хх][аоеи]+х[аоеи]*``, и он сможет распознавать строки “Хааха”, “хееееех” и “Хихии”.
+
+## Ленивая квантификация
+
+Предположим, перед нами стоит задача — найти все HTML-теги в строке
+
+```html
+<p><b>Tproger</b> — мой <i>любимый</i> сайт о программировании!</p>
+```
+
+Очевидное решение <.*> здесь не сработает — оно найдёт всю строку целиком, т.к. она начинается с тега абзаца и им же заканчивается. То есть содержимым тега будет считаться строка
+
+```
+p><b>Tproger</b> — мой <i>любимый</i> сайт о программировании!</p
+```
+
+Это происходит из-за того, что по умолчанию квантификатор работают по т.н. жадному алгоритму — старается вернуть как можно более длинную строку, соответствующую условию. Решить проблему можно двумя способами. Первый — использовать выражение ``<[^>]*>``, которое запретит считать содержимым тега правую угловую скобку. Второй — объявить квантификатор не жадным, а ленивым. Делается это с помощью добавления справа к квантификатору символа ?. Т.е. для поиска всех тегов выражение обратится в ``<.*?>``.
+
+## Ревнивая квантификация
+
+Иногда для увеличения скорости поиска (особенно в тех случаях, когда строка не соответствует регулярному выражению) можно использовать запрет алгоритму возвращаться к предыдущим шагам поиска для того, чтобы найти возможные соответствия для оставшейся части регулярного выражения. Это называется ревнивой квантификацией. Квантификатор делается ревнивым с помощью добавления к нему справа символа +. Ещё одно применение ревнивой квантификации — исключение нежелательных совпадений. Так, паттерну ab*+a в строке “ababa” будут соответствовать только первые три символа, но не символы с третьего по пятый, т.к. символ “a”, который стоит на третьей позиции, уже был использован для первого результата.
+
+## Скобочные группы
+
+Для нашего шаблона “смеющегося” междометия осталась самая малость — учесть, что буква “х” может встречаться более одного раза, например, “Хахахахааахахооо”, а может и вовсе заканчиваться на букве “х”. Вероятно, здесь нужно применить квантификатор для группы [аиое]+х, но если мы просто напишем [аиое]х+, то квантификатор + будет относиться только к символу “х”, а не ко всему выражению. Чтобы это исправить, выражение нужно взять в круглые скобки: ([аиое]х)+.
+
+Таким образом, наше выражение превращается в [Хх]([аиое]х?)+ — сначала идёт заглавная или строчная “х”, а потом произвольное ненулевое количество гласных, которые (возможно, но не обязательно) перемежаются одиночными строчными “х”. Однако это выражение решает проблему лишь частично — под это выражение попадут и такие строки, как, например, “хихахех” — кто-то может быть так и смеётся, но допущение весьма сомнительное. Очевидно, мы можем использовать набор из всех гласных лишь единожды, а потом должны как-то опираться на результат первого поиска. Но как?…
+
+## Запоминание результата поиска по группе (обратная связь)
+
+Оказывается, результат поиска по скобочной группе записывается в отдельную ячейку памяти, доступ к которой доступен для использования в последующих частях регулярного выражения. Возвращаясь к задаче с поиском HTML-тегов на странице, нам может понадобиться не только найти теги, но и узнать их название. В этом нам может помочь регулярное выражение <(.*?)>.
+
+```html
+<p><b>Tproger</b> — мой <i>любимый</i> сайт о программировании!</p>
+```
+
+Результат поиска по всем регулярному выражению: “<p>”, “<b>”, “</b>”, “<i>”, “</i>”, “</p>”.
+
+Результат поиска по первой группе: “p”, “b”, “/b”, “i”, “/i”, “/i”, “/p”.
+
+На результат поиска по группе можно ссылаться с помощью выражения \n, где n — цифра от 1 до 9. Например выражению (\w)(\w)\1\2 соответствуют строки “aaaa”, “abab”, но не соответствует “aabb”.
+
+Если выражение берётся в скобки только для применения к ней квантификатора (не планируется запоминать результат поиска по этой группе), то сразу после первой скобки стоит добавить ?:, например ``(?:[abcd]+\w)``.
+
+С использованием этого механизма мы можем переписать наше выражение к виду ``[Хх]([аоие])х?(?:\1х?)*``.
+
+
+## Перечисление
+
+Чтобы проверить, удовлетворяет ли строка хотя бы одному из шаблонов, можно воспользоваться аналогом булевого оператора OR, который записывается с помощью символа "|". Так, под шаблон ``Анна|Одиночество`` попадают строки “Анна” и “Одиночество” соответственно. Особенно удобно использовать перечисления внутри скобочных групп. Так, например (?:a|b|c|d) полностью эквивалентно [abcd] (в данном случае второй вариант предпочтительнее в силу производительности и читаемости).
+
+С помощью этого оператора мы сможем добавить к нашему регулярному выражению для поиска междометий возможность распознавать смех вида “Ахахаах” — единственной усмешке, которая начинается с гласной: ``[Хх]([аоие])х?(?:\1х?)*|[Аа]х?(?:ах?)+``
+
+## Особые символы ищут символы по специальным правилам:
+
+* \t — табуляция
+* \n — новая строка
+* \r — возврат каретки (два последних символа унаследованы компьютерами от эпохи пишущих машинок, когда для начала печати с новой строки необходимо было выполнить два действия — возврат каретки в начало строки и перевод каретки на новую строку)
+* \s — произвольный вид пробела (пробел, табуляция, новая строка, возврат каретки)
+* \d — произвольная цифра, аналог [0-9]
+* \w — произвольный «символ в слове», обычно аналог [a-zA-z0-9], то есть, латинская буква или цифра
+* \S — НЕ пробел, \D — НЕ цифра, \W — НЕ «символ в слове»
+
+## [(?...)](https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D1%8B%D0%B5_%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F#%D0%9A%D0%B2%D0%B0%D0%BD%D1%82%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%86%D0%B8%D1%8F_(%D0%BF%D0%BE%D0%B8%D1%81%D0%BA_%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D0%B5%D0%B9))
+
+### Группировка без обратной связи (?:...)
+
+Если группа используется только для группировки и её результат в дальнейшем не потребуется, то можно использовать группировку вида (?:шаблон). Под результат такой группировки не выделяется отдельная область памяти и, соответственно, ей не назначается номер. Это положительно влияет на скорость выполнения выражения, но понижает удобочитаемость.
+
+### Атомарная группировка (?>...)
+
+Атомарная группировка вида (?>шаблон) также, как и группировка без обратной связи, не создаёт обратных связей. В отличие от неё, такая группировка запрещает возвращаться назад по строке, если часть шаблона уже найдена.
+
+>### Модификаторы (В котлине не поддерживаются)
+>
+>Модификаторы действуют с момента вхождения и до конца регулярного выражения или противоположного модификатора. Некоторые интерпретаторы могут применить модификатор ко всему выражению, а не с момента его вхождения.
+>
+>#### нечувствительность выражения к регистру символов (англ. case insensitivity)
+>* (?i) - Включает
+>* (?-i) - Выключает
+>
+>#### режим соответствия точки символам переноса строки и возврата >каретки
+>* (?s) - Включает
+>* (?-s) - Выключает
+>
+>#### Комментарии
+>
+>Для добавления комментариев в регулярное выражение можно использовать группы-комментарии вида (?#комментарий). Такая группа интерпретатором полностью игнорируется и не проверяется на вхождение в текст. Например, выражение А(?#тут комментарий)Б соответствует строке АБ.
+
+
+### Просмотр вперёд и назад
+
+В большинстве реализаций регулярных выражений есть способ производить поиск фрагмента текста, «просматривая» (но не включая в найденное) окружающий текст, который расположен до или после искомого фрагмента текста. Просмотр с отрицанием используется реже и «следит» за тем, чтобы указанные соответствия, напротив, не встречались до или после искомого текстового фрагмента.
+
+Представление | Вид просмотра | Пример | Соответствие
+--------------|---------------|--------|-------------
+(?=шаблон) | Позитивный просмотр вперёд | Людовик(?=XVI) | ЛюдовикXV, **Людовик**XVI, **Людовик**XVIII, ЛюдовикLXVII, ЛюдовикXXL
+(?!шаблон) | Негативный просмотр вперёд (с отрицанием) | Людовик(?!XVI) | **Людовик**XV, ЛюдовикXVI, ЛюдовикXVIII, **Людовик**LXVII, **Людовик**XXL
+(?<=шаблон) | Позитивный просмотр назад | (?<=Сергей )Иванов | Сергей **Иванов**, Игорь Иванов
+(?<!шаблон) | Негативный просмотр назад (с отрицанием) | (?<!Сергей )Иванов | Сергей Иванов, Игорь **Иванов**
+
+### Поиск по условию
+
+Во многих реализациях регулярных выражений существует возможность выбирать, по какому пути пойдёт проверка в том или ином месте регулярного выражения на основании уже найденных значений.
+
+Представление | Пояснение | Пример | Соответствие
+--------------|-----------|--------|--------------
+(?:(?=если)то\|иначе) | Если операция просмотра успешна, то далее выполняется часть то, иначе выполняется часть иначе. В выражении может использоваться любая из четырёх операций просмотра. Следует учитывать, что операция просмотра нулевой ширины, поэтому части то в случае позитивного или иначе в случае негативного просмотра должны включать в себя описание шаблона из операции просмотра. | (?:(?<=а)м\|п) | ма**м**, **п**ап
+(?(n)то\|иначе) | Если n-я группа вернула значение, то поиск по условию выполняется по шаблону то, иначе по шаблону иначе. | (а)?(?(1)м\|п) | м**ам**, **п**а**п**
+
+### Флаги
+
+Регулярные выражения могут иметь флаги, которые влияют на поиск.
+
+В Котлине флаги задаются вторым параметром в конструкторе
+
+Флаг | Действие
+-----|---------
+IGNORE_CASE | Регистронезависимый поиск
+MULTILINE | Мультистроковый поиск
+LITERAL |
+UNIX_LINES |
+COMMENTS |
+CANON_EQ |
+
+## Регулярные выражения в Котлине
+
+Для описания регулярных выражений в Котлине используется тип Regex. Для создания регулярного выражения следует вызвать его конструктор, например Regex("KotlinAsFirst"). Второй способ создания регулярного выражения — вызов функции toRegex() на строке-получателе, например "KotlinAsFirst".toRegex().
+
+При создании регулярных выражений вместо обычных строк в двойных кавычках рекомендуется использовать так называемые raw string literals (необработанные строки). Перед и после такого литерала должны стоять три двойных кавычки. Внутри необработанных строк не применяется экранирование, что позволяет применять специфичные для регулярных выражений символы без дополнительных ухищрений. Например: Regex("""x|+|-|\*|/|\(|\)|\d+?| +?""") — задаёт выражение x, или +, или -, или …​, или число, или любое количество пробелов. Без тройных кавычек нам пришлось бы дважды записать каждый из \.
+
+```kt
+fun main(args: Array<String>){
+    // регулярное выражение создано конструтором с одним флагом
+    val regex = Regex("""(\d+)""", RegexOption.IGNORE_CASE)
+    val res = regex.find("найдет только число 99, а число 22 не найдет")
+    if(res!=null)
+        for (r in res.groupValues)
+            println(r)
+}
+```
+
+Программа выдаст:
+
+```
+99
+99
+```
+
+Во втором варианте создадим регулярное выражение через метод строки:
+
+```kt
+fun main(args: Array<String>){
+    val regex = """(\d+)""".toRegex(setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE))
+    val res = regex.findAll("найдет число 99, и число 22 тоже")
+    if(res!=null)
+        for (r in res)
+            for(r2 in r.groupValues.drop(1))
+                println(r2)
+}
+```
+
+Программа выдаст:
+
+```
+99
+22
+```
+
+Для анализа результата поиска применяется тип MatchResult, который можно получить, вызвав find на регулярном выражении-получатале: Regex("""…​""").find(string, startIndex). find ищет первое вхождение регулярного выражения в строку string, начиная с индекса startIndex (по умолчанию — 0). Если вхождений регулярного выражения не найдено, результат find равен null.
+
+Regex("""…​""").findAll(string, startIndex) ищет ВСЕ вхождения регулярного выражения, которые после этого можно перебрать с помощью цикла for.
+
+Тип MatchResult включает в себя следующие свойства:
+
+* result.value — подстрока исходной строки, с которой совпало регулярное выражение (совпадение)
+* result.range — интервал индексов символов, в котором было найдено совпадение
+* result.groupValues — список строк, 0-й элемент которого содержит всё регулярное выражение, а последующие содержат значения групп поиска из регулярного выражения (то есть размер списка равен числу групп поиска в выражении + 1)
+
+Некоторые другие полезные методы, связанные:
+
+* Regex("""…​""").replace("MyString", "Replacement") — находит в данной строке все вхождения регулярного выражения и заменяет их на `"Replacement"
+* "MyString".contains(Regex("""…​""")) — есть ли в данной строке хоть одно вхождение регулярного выражения
+* Regex("""…​""").containsMatchIn("MyString") — то же самое, но в другом порядке
+* "MyString".matches(Regex("""…​""")) — соответствует ли данная строка данному регулярному выражению
+* Regex("""…​""").matches("MyString") — то же самое, но в другом порядке
+* Regex("""…​""").split("MyString") — деление строки на части с использованием заданного регулярного выражения как разделителя
+
+Мини-пример:
+
+```kt
+fun timeStrToSeconds(str: String): Int {
+    val matchResult = Regex("""(\d\d):(\d\d):(\d\d)""").find(str)
+    if (matchResult == null) return -1
+    return matchResult.groupValues.drop(1).map { it.toInt() }.fold(0) {
+        previous, next -> previous * 60 + next
+    }
+}
+```
+
+Здесь мы разбираем исходную строку вида «12:34:56» с целью найти в ней три одинаковых группы поиска (\d\d). Каждая из групп поиска включает в себя две цифры. Убедившись с помощью проверки на null, что регулярное выражение успешно найдено, мы отбрасываем первый элемент groupValues с помощью функции drop(1), оставляя, таким образом, в списке только значения трёх групп поиска. Далее каждая из пар цифр конвертируется в число. Результат сворачивается в число секунд, прошедших с начала дня, с помощью функции высшего порядка fold
+
+# Задания для закрепления
+
+## Найдите время
+
+Время имеет формат часы:минуты. И часы, и минуты состоят из двух цифр, пример: 09:00. Напишите регулярное выражение для поиска времени в строке: “Завтрак в 09:00”. Учтите, что “37:98” – некорректное время.
+
+## Java[^script]
+
+Найдет ли регулярка Java[^script] что-нибудь в строке Java? А в строке JavaScript?
+
+## Цвет
+
+Напишите регулярное выражение для поиска HTML-цвета, заданного как #ABCDEF, то есть символ "#" и затем 6 шестнадцатеричных символов.
+
+## Разобрать арифметическое выражение
+
+Арифметическое выражение состоит из двух чисел и операции между ними, например:
+
+* 1 + 2
+* 1.2 *3.4
+* -3/ -6
+* -2-2
+
+Список операций: “+”, «-», “*” и “/”.
+
+Также могут присутствовать пробелы вокруг оператора и чисел.
+
+Напишите регулярное выражение, которое найдёт как всё арифметическое действие, так и (через группы) два операнда.
+
+## Найдите все натуральные числа (возможно, окружённые буквами);
+
+## Найдите все «слова», написанные капсом (то есть строго заглавными), возможно внутри настоящих слов (аааБББввв);
+
+## Найдите слова, в которых есть русская буква, а когда-нибудь за ней цифра;
+
+## Найдите все слова, начинающиеся с русской или латинской большой буквы (\b — граница слова);
+
+## Найдите слова, которые начинаются на гласную (\b — граница слова);
+
+## Найдите все натуральные числа, не находящиеся внутри или на границе слова;
+
+## Найдите строчки, в которых есть символ * (. — это точно не конец строки!);
+
+## Найдите строчки, в которых есть открывающая и когда-нибудь потом закрывающая скобки;
+
+## Найдите пустые строчки;

+ 604 - 0
articles/skv.md

@@ -0,0 +1,604 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Методы программирования](./articles/t2l2.md) | [Содержание](../readme.md#тема-2-языки-и-методы-программирования) | [Основные элементы языка.](./articles/t3l1.md)
+
+# Системы контроля версий
+
+>🕮 таким символом помечены команды или действия, которые нужно выполнить в лабораторной работе или домашнем задании
+
+## Что такое контроль версий, и зачем он нужен? 
+
+Система контроля версий (**СКВ**) — это система, регистрирующая изменения в одном или нескольких файлах с тем, чтобы в дальнейшем была возможность вернуться к определённым старым версиям этих файлов (типичный пример: выложили версию в проду, начали работу над новыми фичами и вдруг обнаружились ошибки. Нужно не потеряв новых наработок вернуться к рабочей версии, исправить ошибки).
+
+Можно просто копировать проект в другой каталог, такой подход часто применяется из-за своей простоты, но имеет множество недостатков:
+
+- избыточность (дублируется весь код, а не только изменения)
+- нет механизмов для распределения работы между несколькими разработчиками
+- нет данных о том что именно изменилось (обычно пишут history файл с общей информацией об изменениях)
+
+>## Локальные системы контроля версий (для самостоятельного ознакомления)
+>
+>Для решения части из этих проблем были разработаны локальные **СКВ**.  
+Одной из первых и наиболее популярных СКВ такого типа являлась *RCS*, которая была разработана в 1985 году.  
+Для каждого файла, зарегистрированного в системе, она хранит полную историю изменений, причём для текстовых файлов используется эффективный алгоритм дельта-компрессии, когда хранится только последняя версия и все межверсионные изменения.  
+Такие **СКВ** решали только первую проблему - избыточность данных.
+
+Современные **СКВ** можно разделить на две группы: централизованные и распределенные.
+
+## Централизованные системы контроля версий
+
+Следующей основной проблемой оказалась необходимость сотрудничать с разработчиками за другими компьютерами. Чтобы решить её, были созданы централизованные системы контроля версий (**ЦСКВ**). В таких системах, например **CVS**, **Subversion** и **Perforce**, есть центральный сервер, на котором хранятся все файлы под версионным контролем, и ряд клиентов, которые получают копии файлов из него.  Много лет это было стандартом для систем контроля версий.
+
+Такой подход имеет множество преимуществ, особенно перед локальными **СКВ**. К примеру, все знают, кто и чем занимается в проекте. У администраторов есть чёткий контроль над тем, кто и что может делать, и, конечно, администрировать **ЦСКВ** намного легче, чем локальные базы на каждом клиенте.
+
+Однако при таком подходе есть и несколько серьёзных недостатков. Наиболее очевидный — централизованный сервер является уязвимым местом всей системы. Если сервер выключается на час, то в течение часа разработчики не могут взаимодействовать, и никто не может сохранить новой версии своей работы. Если же повреждается диск с центральной базой данных и нет резервной копии, вы теряете абсолютно всё — всю историю проекта, разве что за исключением нескольких рабочих версий, сохранившихся на рабочих машинах пользователей. Локальные системы контроля версий подвержены той же проблеме: если вся история проекта хранится в одном месте, вы рискуете потерять всё.
+
+## Распределённые системы контроля версий
+
+И в этой ситуации в игру вступают распределённые системы контроля версий. В таких системах как **Git**, **Mercurial**, **Bazaar** или **Darcs** клиенты не просто выгружают последние версии файлов, а полностью копируют весь репозиторий (репозиторий, в простонародье репа, это место, где хранятся и поддерживаются какие-либо данные). При этом можно выделить центральный репозиторий (условно), в который будут отправляться изменения из локальных и, с ним же эти локальные репозитории будут синхронизироваться. Поэтому в случае, когда "умирает" сервер, через который шла работа, любой клиентский репозиторий может быть скопирован обратно на сервер, чтобы восстановить базу данных. Каждый раз, когда клиент забирает свежую версию файлов, он создаёт себе полную копию всех данных.  
+Кроме того, в большей части этих систем можно работать с несколькими удалёнными репозиториями.
+
+На сегодняшний день стандартом де-факто стала распределенная система контроля версий - **GIT**, но в старых больших проектах вполне могут встретиться устаревшие **СКВ** (например, популярная в свое время **Subversion**).
+
+В дальнейшем, все изложение будет касаться только **GIT**.
+
+## Основы GIT
+
+### Почти все операции — локальные
+
+Для совершения большинства операций в **Git**'е необходимы только локальные файлы и ресурсы, т.е. обычно информация с других компьютеров в сети не нужна.
+
+К примеру, чтобы показать историю проекта, **Git**'у не нужно скачивать её с сервера, он просто читает её прямо из вашего локального репозитория. Поэтому историю вы увидите практически мгновенно. Если вам нужно просмотреть изменения между текущей версией файла и версией, сделанной месяц назад, **Git** может взять файл месячной давности и вычислить разницу на месте, вместо того чтобы запрашивать разницу у **СКВ**-сервера или качать с него старую версию файла и делать локальное сравнение.
+
+Кроме того, работа локально означает, что практически всё можно сделать без доступа к Сети. Если вы в самолёте или в поезде и хотите немного поработать, можно спокойно делать коммиты, а отправить их, как только станет доступна сеть. Во многих других системах это невозможно или же крайне неудобно. Например, используя **Perforce**, вы мало что можете сделать без соединения с сервером. Работая с **Subversion** и **CVS**, вы можете редактировать файлы, но сохранить изменения в вашу базу данных нельзя (потому что она отключена от репозитория). 
+
+### Git следит за целостностью данных
+
+Перед сохранением любого файла **Git** вычисляет контрольную сумму, и она становится индексом этого файла. Поэтому невозможно изменить содержимое файла или каталога так,чтобы **Git** не узнал об этом. Эта функциональность встроена в сам фундамент **Git**'а и является важной составляющей его философии. Если информация потеряется при передаче или повредится на диске, **Git** всегда это выявит.
+
+### Чаще всего данные в Git только добавляются
+
+Практически все действия, которые вы совершаете в **Git**'е, только добавляют данные в базу. Очень сложно заставить систему удалить данные или сделать что-то неотменяемое. Можно, как и в любой другой **СКВ**, потерять данные, которые вы ещё не сохранили, но как только они зафиксированы, их очень сложно потерять, особенно если вы регулярно отправляете изменения в другой репозиторий.
+
+### Три состояния файлов
+
+Теперь внимание. Это самое важное, что нужно помнить про **Git**, если вы хотите, чтобы дальше изучение шло гладко. В **Git**'е файлы могут находиться в одном из трёх состояний: _зафиксированном_, _изменённом_ и _подготовленном_. "_Зафиксированный_" значит, что файл уже сохранён в вашей локальной базе. К _изменённым_ относятся файлы, которые поменялись, но ещё не были зафиксированы. _Подготовленные_ файлы — это изменённые файлы, отмеченные для включения в следующий коммит.
+
+Стандартный рабочий процесс с использованием Git'а выглядит примерно так:
+
+- Вы вносите _изменения_ в файлы в своём рабочем каталоге.
+- _Подготавливаете_ файлы, добавляя их слепки в область подготовленных файлов.
+- Делаете коммит, который берёт подготовленные файлы и помещает их в каталог **Git**'а на постоянное хранение (_фиксируете_ изменения).
+
+## Установка *GIT* (Windows)
+
+Установить **Git** в Windows очень просто. Просто скачайте дистрибутив и запустите его:
+
+* [Git для Windows](https://gitforwindows.org)
+
+После установки у вас будет как консольная версия (включающая SSH-клиент, который пригодится позднее), так и стандартная графическая.
+
+## Первоначальная настройка Git
+
+Теперь, когда **Git** установлен в Вашей системе, нужно настроить среду для работы с **Git**'ом под себя. Это необходимо сделать только один раз — при обновлении версии **Git**'а настройки сохранятся. Но вы можете поменять их в любой момент, выполнив те же команды снова.
+
+В состав **Git**'а входит утилита `git config`, которая позволяет просматривать и устанавливать параметры, контролирующие все аспекты работы **Git**'а и его внешний вид. 
+
+### Имя пользователя
+
+Первое, что вам следует сделать после установки **Git**'а, — указать ваше имя и адрес электронной почты. Это важно, потому что каждый коммит в **Git**'е содержит эту информацию, и она включена в коммиты, передаваемые вами, и не может быть далее изменена (команды выполняются из командной строки):
+
+Перейдите в каталог с проектом (создайте, если его еще нет).
+
+```
+git config user.name "Вася Пупкин"
+git config user.email vpupkin@example.com
+```
+
+Если добавить в эти команды опцию `--global`, то эти настройки запишутся в настройки пользователя и будут действовать на все проекты. Мы выполняем эту команду без параметра `--global`, чтобы настройки касались только вашего проекта.
+
+Перед началом работы с командами **GIT** определимся с сервером для центрального репозитория.
+
+## Выбор сервера для центрального репозитория
+
+Есть два варианта:
+
+- собственный сервер в локальной сети, обычно используется в крупных компаниях или из-за режима секретности (например, gitlab, mercurial-server)
+- сервер в Сети, например github, bitbucket (atlassian), gitlab. На таких серверах обычно есть бесплатные аккаунты для публичных репозиториев (публичный репозиторий доступен всем), в последнее время в бесплатных аккаунтах появляется возможность создания и приватных репозиториев (с приватным репозиторием может работать только создатель (администратор) и пользователи, которых назначатет администратор. Администратор имеет полные права на репозторий, пользователи могут иметь права на запись или только на чтение). На сервере atlassian поддерживаются репозитории для git и mercurial, в бесплатном аккаунте есть возможность создавать приватные репозитории (количество не ограничено, но ограничено общее дисковое пространство под репозитории и количество разработчиков - не более 5). На гитхабе возможность создания бесплатных аккаунтов появилась совсем недавно (январь 2019 года), приватных репозиториев можно создать только 3.
+
+Мы будем использовать сервер **GOGS** (GO Git Server), расположенный по адресу https://kolei.ru.
+
+## Базовые команды Git
+
+Здесь рассмотрены базовые команды, необходимые вам для решения подавляющего большинства задач возникающих при работе с **Git**'ом. После изучения этой части вы научитесь настраивать и инициализировать репозиторий, начинать и прекращать версионный контроль файлов, а также подготавливать и фиксировать изменения. Мы также узнаем как настроить в **Git**'е игнорирование отдельных файлов или их групп, как быстро и просто отменить ошибочные изменения, как просмотреть историю вашего проекта и изменения между отдельными коммитами (_commit_), а также как отправлять (_push_) и получать (_pull_) изменения в/из удалённого (_remote_) репозитория.
+
+### Создание Git-репозитория
+
+Для создания **Git**-репозитория существуют два основных подхода. Первый подход — импорт в **Git** уже существующего проекта или каталога. Второй — клонирование уже существующего репозитория с сервера.
+
+#### Создание репозитория в существующем каталоге
+
+Если вы собираетесь начать использовать **Git** для существующего проекта, то вам необходимо перейти в каталог проекта и в командной строке ввести
+
+
+>🕮 не забудьте создать пустой каталог для проекта и выполняйте все команды в нём
+
+```
+git init
+```
+
+>Запоминать эти команды не нужно - они показываются при создании нового репозитория.
+
+Эта команда создаёт в текущем каталоге новый подкаталог с именем `.git` содержащий все необходимые файлы репозитория — основу **Git**-репозитория. На этом этапе ваш проект ещё не находится под версионным контролем.
+
+Если вы хотите добавить под версионный контроль существующие файлы, вам стоит проиндексировать эти файлы и осуществить первую фиксацию изменений. Осуществить это вы можете с помощью нескольких команд `git add` указывающих индексируемые файлы, а затем `git commit`:
+
+```
+git add .
+git commit -m "initial project version"
+```
+
+Мы разберём, что делают эти команды чуть позже. На данном этапе, у нас есть **Git**-репозиторий с добавленными файлами и начальным коммитом.
+
+#### Клонирование существующего репозитория
+
+Если вы хотите получить копию существующего репозитория **Git**, например, проекта, в котором вы хотите поучаствовать, то вам нужна команда `git clone`.
+
+Клонирование репозитория осуществляется командой `git clone <url>`. Для примера склонируем репозиторий с этими лекциями:
+
+```
+git clone https://kolei.ru/ekolesnikov/OAP
+```
+
+Эта команда создаёт в текущем каталоге каталог с именем клонируемого проекта (`OAP`), инициализирует в нём каталог `.git`, скачивает все данные для этого репозитория и создаёт рабочую копию последней версии. Если вы зайдёте в новый каталог `OAP`, вы увидите в нём файлы, пригодные для работы и использования. 
+
+Если вы хотите клонировать репозиторий в каталог, отличный от `OAP`, то можно это указать в следующем параметре командной строки:
+
+```
+git clone https://kolei.ru/ekolesnikov/OAP mydir
+```
+
+Эта команда делает всё то же самое, что и предыдущая, только результирующий каталог будет назван `mydir`.
+
+#### Запись изменений в репозиторий
+
+Итак, у вас имеется **Git**-репозиторий и рабочая копия файлов для некоторого проекта. Вам нужно делать некоторые изменения и фиксировать эти изменения в вашем репозитории каждый раз, когда проект достигает состояния, которое вам хотелось бы сохранить (обычно рекомендуют фиксировать каждое атомарное изменение, т.е. функцию, класс или законченный алгорим).
+
+Каждый файл в вашем рабочем каталоге может находиться в одном из двух состояний: под версионным контролем (отслеживаемые) и нет (неотслеживаемые). Отслеживаемые файлы — это те файлы, которые были в последнем слепке состояния проекта; они могут быть неизменёнными, изменёнными или подготовленными к коммиту. Неотслеживаемые файлы — это всё остальное, любые файлы в вашем рабочем каталоге, которые не входили в ваш последний слепок состояния и не подготовлены к коммиту. Когда вы впервые клонируете репозиторий, все файлы будут отслеживаемыми и неизменёнными, потому что вы только взяли их из хранилища и ничего пока не редактировали.
+
+Как только вы отредактируете файлы, **Git** будет рассматривать их как изменённые, т.к. вы изменили их с момента последнего коммита. Вы индексируете эти изменения и затем фиксируете все индексированные изменения, а затем цикл повторяется.
+
+#### Определение состояния файлов
+
+Основной инструмент, используемый для определения, какие файлы в каком состоянии находятся — это команда `git status`. Если вы выполните эту команду сразу после клонирования, вы увидите что-то вроде этого:
+
+>🕮
+
+```
+git status
+On branch master
+nothing to commit
+```
+
+Это означает, что у вас чистый рабочий каталог, другими словами — в нём нет отслеживаемых изменённых файлов. **Git** также не обнаружил неотслеживаемых файлов, в противном случае они бы были перечислены здесь. И наконец, команда сообщает вам на какой ветке (_branch_) вы сейчас находитесь. Пока что это всегда ветка **master** — это ветка по умолчанию.
+
+Предположим, вы добавили в свой проект новый файл, простой файл `README.MD`. Если этого файла раньше не было, и вы выполните `git status`, вы увидите свой неотслеживаемый файл вот так:
+
+>🕮
+
+```
+git status
+On branch master
+Untracked files:
+  (use "git add <file>..." to include in what will be committed)
+
+README.MD
+nothing added to commit but untracked files present (use "git add" to track)
+```
+
+Понять, что новый файл `README.MD` неотслеживаемый можно по тому, что он находится в секции "Untracked files" в выводе команды `git status`. Статус "неотслеживаемый файл", по сути, означает, что **Git** видит файл, отсутствующий в предыдущем снимке состояния (коммите); **Git** не станет добавлять его в ваши коммиты, пока вы его явно об этом не попросите. Это предохранит вас от случайного добавления в репозиторий сгенерированных бинарных файлов или каких-либо других, которые вы и не думали добавлять. Мы хотели добавить `README.MD`, так давайте сделаем это.
+
+#### Отслеживание новых файлов
+
+Для того чтобы начать отслеживать (добавить под версионный контроль) новый файл, используется команда `git add`. Чтобы начать отслеживание файла `README.MD`, вы можете выполнить следующее:
+
+>🕮
+
+```
+git add README.MD
+```
+
+Если вы снова выполните команду `git status`, то увидите, что файл `README.MD` теперь отслеживаемый и индексированный:
+
+>🕮
+
+```
+git status
+On branch master
+Changes to be committed:
+  (use "git reset HEAD <file>..." to unstage)
+
+  new file:   README.MD
+```
+
+Вы можете видеть, что файл проиндексирован по тому, что он находится в секции “Changes to be committed”. Если вы выполните коммит в этот момент, то версия файла, существовавшая на момент выполнения вами команды `git add`, будет добавлена в историю снимков состояния. Как вы помните, когда вы ранее выполнили `git init`, вы затем выполнили `git add (файлы)` — это было сделано для того, чтобы добавить файлы в вашем каталоге под версионный контроль. Команда `git add` принимает параметром путь к файлу или каталогу, если это каталог, команда рекурсивно добавляет (индексирует) все файлы в данном каталоге.
+
+#### Игнорирование файлов
+
+Зачастую, имеется группа файлов, которые вы не только не хотите автоматически добавлять в репозиторий, но и видеть в списках неотслеживаемых. К таким файлам обычно относятся автоматически генерируемые файлы (различные логи, результаты сборки программ и т.п.). В таком случае, вы можете создать в корне проекта файл `.gitignore` с перечислением шаблонов соответствующих таким файлам. Вот пример файла `.gitignore`:
+
+>🕮 создайте файл `.gitignore` в корне проекта (там же где и файл `readme.md`)
+
+```
+*.log
+*.~*
+```
+
+Первая строка предписывает **Git**'у игнорировать любые файлы заканчивающиеся на `.log` — файлы логов. Вторая строка предписывает игнорировать все файлы расширение которых начинается на тильду (`~`), такие расширения обычно используются для обозначения временных файлов. Вы можете также включить каталоги `log`, `tmp`. Хорошая практика заключается в настройке файла `.gitignore` до того, как начать серьёзно работать, это защитит вас от случайного добавления в репозиторий файлов, которых вы там видеть не хотите.
+
+К шаблонам в файле `.gitignore` применяются следующие правила:  
+
+* Пустые строки, а также строки, начинающиеся со знака `#`, игнорируются.
+* Можно использовать стандартные glob шаблоны.
+
+    Glob-шаблоны представляют собой упрощённые регулярные выражения используемые командными интерпретаторами. Символ `*` соответствует 0 или более символам; последовательность `[abc]` — любому символу из указанных в скобках (в данном примере `a`, `b` или `c`); знак вопроса (`?`) соответствует одному символу; `[0-9]` соответствует любому символу из интервала (в данном случае от `0` до `9`).  
+
+* Можно заканчивать шаблон символом слэша (`/`) для указания каталога.
+* Можно инвертировать шаблон, использовав восклицательный знак (`!`) в качестве первого символа.
+
+
+Вот ещё один пример файла `.gitignore`:
+
+```
+# комментарий — эта строка игнорируется
+# не обрабатывать файлы, имя которых заканчивается на .a
+*.a
+# НО отслеживать файл lib.a, несмотря на то, что мы игнорируем все .a файлы с помощью предыдущего правила
+!lib.a
+# игнорировать только файл TODO находящийся в корневом каталоге, не относится к файлам вида subdir/TODO
+/TODO
+# игнорировать все файлы в каталоге build/
+build/
+# игнорировать doc/notes.txt, но не doc/server/arch.txt
+doc/*.txt
+# игнорировать все .txt файлы в каталоге doc/
+doc/**/*.txt
+```
+
+#### Просмотр индексированных и неиндексированных изменений
+
+Если результат работы команды `git status` недостаточно информативен для вас — вам хочется знать, что конкретно поменялось, а не только какие файлы были изменены — вы можете использовать команду `git diff`.  
+Подробно на этой команде останавливаться не будем, так как многие современные средства разработки (IDE - Интегрированная среда разработки) имеют встроенную поддержку команд **GIT** и изменения в файлах там наглядно отображаются.
+
+#### Фиксация изменений
+
+Теперь, когда ваш индекс настроен так, как вам и хотелось, вы можете зафиксировать свои изменения. Запомните, всё, что до сих пор не проиндексировано — любые файлы, созданные или изменённые вами, и для которых вы не выполнили `git add` после момента редактирования — не войдут в этот коммит. Они останутся изменёнными файлами на вашем диске. В нашем случае,когда вы в последний раз выполняли `git status`, вы видели что всё проиндексировано, и вот, вы готовы к коммиту. Простейший способ зафиксировать изменения — это выполнить команду `git commit`. Так же можно воспользоваться встроенными в IDE средствами.
+
+```
+git commit   
+```
+
+Эта команда откроет выбранный вами текстовый редактор. (Редактор устанавливается системной переменной $EDITOR, вы можете установить ваш любимый с помощью команды `git config  core.editor`).  
+
+В редакторе будет отображён следующий текст (это пример окна Vim'а):  
+```
+# Please enter the commit message for your changes. Lines starting  
+# with '#' will be ignored, and an empty message aborts the commit.  
+# On branch master  
+# Changes to be committed:  
+#   (use "git reset HEAD <file>..." to unstage)  
+#  
+#       new file:   README  
+#       modified:   benchmarks.rb  
+~  
+~  
+~  
+".git/COMMIT_EDITMSG" 10L, 283C  
+```
+
+Вы можете видеть, что комментарий по умолчанию для коммита содержит закомментированный результат работы ("выхлоп") команды `git status` и ещё одну пустую строку сверху. Вы можете удалить эти комментарии и набрать своё сообщение или же оставить их для напоминания о том, что вы фиксируете. (Для ещё более подробного напоминания, что же именно вы поменяли, можете передать аргумент `-v` в команду `git commit`. Это приведёт к тому, что в комментарий будет также помещена дельта/diff изменений, таким образом вы сможете точно увидеть всё, что сделано.) Когда вы выходите из редактора, **Git** создаёт для вас коммит с этим сообщением (удаляя комментарии и вывод diff'а).  
+
+Можно сразу набрать свой комментарий к коммиту в командной строке вместе с командой `git commit`, указав его после параметра `-m`:
+
+>🕮
+
+```
+git commit -m "мой первый коммит"
+[master]: created 463dc4f: "мой первый коммит"
+ 2 files changed, 3 insertions(+), 0 deletions(-)
+ create mode 100644 README
+```
+
+Итак, вы создали свой первый коммит! Вы можете видеть, что коммит вывел вам немного информации о себе: на какую ветку вы выполнили коммит (**master**), какая контрольная сумма SHA-1 у этого коммита (463dc4f), сколько файлов было изменено, а также статистику по добавленным/удалённым строкам в этом коммите.
+
+Запомните, что коммит сохраняет снимок состояния вашего индекса. Всё, что вы не проиндексировали, так и торчит в рабочем каталоге как изменённое; вы можете сделать ещё один коммит, чтобы добавить эти изменения в репозиторий. Каждый раз, когда вы делаете коммит, вы сохраняете снимок состояния вашего проекта, который позже вы можете восстановить или с которым можно сравнить текущее состояние.
+
+#### Удаление файлов
+
+Для того чтобы удалить файл из **Git**'а, вам необходимо удалить его из отслеживаемых файлов (точнее, удалить его из вашего индекса) а затем выполнить коммит. Это позволяет сделать команда `git rm`, которая также удаляет файл из вашего рабочего каталога, так что вы в следующий раз не увидите его как “неотслеживаемый”.
+
+Если вы просто удалите файл из своего рабочего каталога, он будет показан в секции “Changes not staged for commit” (“Изменённые но не обновлённые” — читай не проиндексированные) вывода команды `git status`:
+
+```
+rm grit.gemspec
+git status
+On branch master
+
+Changes not staged for commit:
+  (use "git add/rm <file>..." to update what will be committed)
+
+      deleted:    grit.gemspec
+```
+
+Затем, если вы выполните команду `git rm`, удаление файла попадёт в индекс:
+
+```
+git rm grit.gemspec
+rm 'grit.gemspec'
+git status
+On branch master
+
+Changes to be committed:
+  (use "git reset HEAD <file>..." to unstage)
+
+    deleted:    grit.gemspec
+```
+
+После следующего коммита файл исчезнет и больше не будет отслеживаться. Если вы изменили файл и уже проиндексировали его, вы должны использовать принудительное удаление с помощью параметра `-f`. Это сделано для повышения безопасности, чтобы предотвратить ошибочное удаление данных, которые ещё не были записаны в снимок состояния и которые нельзя восстановить из **Git**'а.
+
+Другая полезная штука, которую вы можете захотеть сделать — это удалить файл из индекса, оставив его при этом в рабочем каталоге. Другими словами, вы можете захотеть оставить файл на винчестере, и убрать его из-под бдительного ока **Git**'а. Это особенно полезно, если вы забыли добавить что-то в файл `.gitignore` и по ошибке проиндексировали, например, большой файл с логами, или кучу промежуточных файлов компиляции. Чтобы сделать это, используйте опцию `--cached`:
+
+```
+git rm --cached readme.txt
+```
+
+В команду `git rm` можно передавать файлы, каталоги или glob-шаблоны. Это означает, что вы можете вытворять что-то вроде:
+
+```
+git rm log/\*.log
+```
+
+Обратите внимание на обратный слэш (`\`) перед `*`. Он необходим из-за того, что **Git** использует свой собственный обработчик имён файлов вдобавок к обработчику вашего командного интерпретатора. Эта команда удаляет все файлы, которые имеют расширение `.log` в каталоге `log/`. Или же вы можете сделать вот так:
+
+```
+git rm \*~
+```
+
+Эта команда удаляет все файлы, чьи имена заканчиваются на `~`.
+
+#### Просмотр истории коммитов
+
+После того как вы создадите несколько коммитов, или же вы склонируете репозиторий с уже существующей историей коммитов, вы, при желании, можете узнать, что же происходило с этим репозиторием. Наиболее простой и в то же время мощный инструмент для этого — команда `git log`.
+
+>🕮
+
+По умолчанию, `git log` выводит список коммитов созданных в данном репозитории в обратном хронологическом порядке. То есть самые последние коммиты показываются первыми.
+
+### Работа с удалёнными репозиториями
+
+Чтобы иметь возможность совместной работы над каким-либо **Git**-проектом, необходимо знать, как управлять удалёнными репозиториями. Удалённые репозитории — это модификации проекта, которые хранятся в интернете или ещё где-то в сети. Их может быть несколько, каждый из которых, как правило, доступен для вас либо только на чтение, либо на чтение и запись. Совместная работа включает в себя управление удалёнными репозиториями и помещение (_push_) и получение (_pull_) данных в и из них тогда, когда нужно обменяться результатами работы.
+
+#### Отображение удалённых репозиториев
+
+Чтобы просмотреть, какие удалённые серверы у вас уже настроены, следует выполнить команду `git remote`. Она перечисляет список имён-сокращений (алиасов) для всех уже указанных удалённых репозиториев. Если вы склонировали ваш репозиторий, у вас должен отобразиться, по крайней мере, **origin** — это имя по умолчанию, которое **Git** присваивает серверу, с которого вы склонировали.
+
+#### Добавление удалённых репозиториев
+
+Чтобы добавить новый удалённый **Git**-репозиторий под алиасом, к которому будет проще обращаться, выполните `git remote add [алиас] [url]`:
+
+>🕮 Тут использовать реальную команду из репозитория
+
+```
+git remote add pb https://kolei.ru/some_repo
+```
+
+Теперь вы можете использовать в командной строке имя `pb` вместо полного URL. Например, если вы хотите извлечь (_fetch_) всю информацию, которая есть в удаленном репозитории, но нет в вашем, вы можете выполнить `git fetch pb`.
+
+#### Fetch и Pull
+
+Как вы только что узнали, для получения данных из удалённых проектов, следует выполнить:
+
+>🕮
+
+```
+git fetch [имя удал. сервера]
+```
+
+Данная команда связывается с указанным удалённым проектом и забирает все те данные проекта, которых у вас ещё нет.
+
+Когда вы клонируете репозиторий, команда `git clone` автоматически добавляет этот удалённый репозиторий под именем **origin**. Таким образом, `git fetch origin` извлекает все наработки, отправленные (_push_) на этот сервер после того, как вы склонировали его (или получили изменения с помощью _fetch_). Важно отметить, что команда _fetch_ забирает данные в ваш локальный репозиторий, но не сливает их с какими-либо вашими наработками и не модифицирует то, над чем вы работаете в данный момент. Вам необходимо вручную слить эти данные с вашими, когда вы будете готовы.
+
+Если вы хотите слить новые данные с вашими, то вы можете использовать команду `git pull`. Она автоматически извлекает и затем сливает данные из удалённой ветки в вашу текущую ветку. Этот способ может для вас оказаться более простым или более удобным. К тому же по умолчанию команда `git clone` автоматически настраивает вашу локальную ветку **master** на отслеживание удалённой ветки **master** на сервере, с которого вы клонировали (подразумевается, что на удалённом сервере есть ветка **master**). Выполнение `git pull`, как правило, извлекает (_fetch_) данные с сервера, с которого вы изначально склонировали, и автоматически пытается слить (_merge_) их с кодом, над которым вы в данный момент работаете.
+
+#### Push
+
+Когда вы хотите поделиться своими наработками, вам необходимо отправить (_push_) их в главный репозиторий. Команда для этого действия простая: `git push [удал. сервер] [ветка]`. Чтобы отправить вашу ветку **master** на сервер **origin** (повторимся, что клонирование, как правило, настраивает оба этих имени автоматически), вы можете выполнить следующую команду для отправки наработок на сервер:
+
+>🕮
+
+```
+git push origin master
+```
+
+Эта команда срабатывает только в случае, если вы клонировали с сервера, на котором у вас есть права на запись, и если никто другой с тех пор не выполнял команду `git push`. Если вы и кто-то ещё одновременно клонируете, затем он выполняет команду `git push`, а затем команду `git push` выполняете вы, то ваш запрос точно будет отклонён. Вам придётся сначала вытянуть (_pull_) их изменения и объединить с вашими. Только после этого вам будет позволено выполнить `git push`.
+
+#### Итоги
+
+К этому моменту вы умеете выполнять все базовые локальные операции с **Git**'ом: создавать или клонировать репозиторий, вносить изменения, индексировать и фиксировать эти изменения, а также просматривать историю всех изменений в репозитории. Дальше мы рассмотрим самую убийственную особенность **Git**'а — его модель ветвления.
+
+## Ветвление в Git
+
+Почти каждая **СКВ** имеет в какой-то форме поддержку ветвления. Ветвление означает, что вы отклоняетесь от основной линии разработки и продолжаете работу, не вмешиваясь в основную линию. 
+
+Пример ветвления из реального проекта:
+
+![](../img/skv_05.png)
+
+Глубоко в теорию я не полезу, только опишу главные команды.
+
+### **git checkout [название ветки]**
+
+Эта команда переключает ваш локальный репозиторий на указанную ветку. 
+
+Тут нужно учитывать, что если в текущей ветке есть измененные файлы, то поменять ветку нельзя. Нужно либо зафиксировать изменения командами `git add` и `git commit`, либо отложить их командой `git stash`. Учитывая, что фиксировать нужно законченные действия, второй вариант бывает предпочтительнее.
+
+### **git stash**
+
+Команда `git stash` сохраняет незафиксированные изменения (подготовленные и неподготовленные) в отдельном хранилище, чтобы вы могли вернуться к ним позже. Затем происходит откат до исходной рабочей копии.
+
+Теперь вы можете вносить изменения, создавать новые коммиты, переключаться между ветками и выполнять другие операции **Git**. По необходимости отложенные изменения можно будет применить позже.
+
+Отложенные изменения сохраняются в локальном репозитории **Git** и не передаются на сервер при выполнении команды `git push`.
+
+Чтобы вернуть отложенные изменения нужно выполнить команду `git stash pop`.
+
+При извлечении отложенных изменений они удаляются из набора и применяются к рабочей копии.
+
+Вы также можете применить изменения к рабочей копии без удаления из набора отложенных изменений. Для этого воспользуйтесь командой `git stash apply`
+
+Это полезно, если вам нужно применить одни и те же отложенные изменения к нескольким веткам.
+
+Вы можете создать несколько наборов отложенных изменений. Команду `git stash` можно выполнить несколько раз, после чего у вас будет возможность просмотреть список наборов с помощью команды `git stash list`.
+
+Рекомендуем добавлять к отложенным изменениям описание в качестве подсказки. Для этого используется команда `git stash save "сообщение"`
+
+По умолчанию команда `git stash pop` применяет последний набор отложенных изменений: `stash@{0}`
+
+Если вам нужно применить определенный набор ранее отложенных изменений, укажите его идентификатор в качестве последнего аргумента.
+
+### Создание новой ветки
+
+Просто создать ветку можно командой `git branch название_ветки`. 
+
+После этого вы можете продолжить работу в текущей ветке или переключитсья на созданную командой `git checkout название_ветки`.
+
+Можно сразу создать ветку и переключиться на неё командой `git checkout -b название_ветки`.
+
+### Завершение работы с веткой
+
+Когда работа над новой фичей (в ветке) закончена, необходимо перенести изменния в основную ветку. Для этого используется команда `git merge название_ветки`. Т.е. Вы сначала переключаетесь на ту ветку, в которую хотите слить изменения, а затем объединяете их:
+
+```
+git checkout master
+git merge название_ветки
+```
+
+Если кто-то менял те же файлы что и Вы (возможно даже Вы сами что-то меняли в другой ветке) и **GIT** не может автоматически определить куда вставить изменения, то возникнет ошибка слияния. 
+
+Для разрешения конфликта слияния вы должны либо выбрать один из вариантов (старая ветка или новая) или объединить оба варианта.
+
+## Формат MarkDown
+
+В корне репозитория принято размещать файл `readme.md`, в котором описывается что это за репозиторий, для чего он нужен, инструкции по установке, если это необходимо.
+
+Расширение `*.md` как раз и означает, что файл в формате **MarkDown**.
+
+**Markdown** (произносится маркда́ун) — облегчённый язык разметки, созданный с целью обозначения форматирования в простом тексте, с максимальным сохранением его читаемости человеком, и пригодный для машинного преобразования.
+
+Раз это *простой текст*, то редактировать его можно даже в блокноте.
+
+Примеры разметки:
+
+### Текст с выделением
+
+* `_`*выделение*`_` - курсив
+* `**`**сильное выделение**`**` - полужирное начертание
+* `_**`_**комбинация**_`**_` курсива и полужирного начертания
+
+  >Причем без разницы в каком порядке использовать символы `**` и `_`
+
+* `~~`~~зачёркнутый~~`~~` текст
+
+### Программный код
+
+Выделяется знаком апострофа \`, может быть как \``внутри строки`\`
+
+\`\`\`
+```txt
+так 
+отдельным
+блоком
+```
+\`\`\`
+
+>Так можно оформлять не только программный код, но и любой текст, в котором нужно сохранить оригинальное форматирование. Дело в том, что **MarkDown**, как и практически любой язык верстки обрезает пустые строки и пробелы
+
+### Списки
+
+1. 1\. Нумерованные
+2. 2\. Причем порядок цифр
+9. 9\. На результат не влияет
+
+* обычные списки (знак `*`)
+    - с любым уровнем вложенности (знак `-`)
+        1. можно комбинировать нумерованные и не нумерованные списки 
+
+### Заголовки
+
+Создание заголовков производится путём помещения знака решетки перед текстом заголовка. Количество знаков `#` соответствует уровню заголовка. Поддерживается `6` уровней заголовков.
+
+# \# Заголовок первого уровня
+...
+### \#\#\# Заголовок третьего уровня
+...
+###### \#\#\#\#\#\# Заголовок шестого уровня
+
+### Цитаты (комментарии)
+
+>Любой элемент разметки (текст, список, картинка) могут быть помечены как цитата, если добавить в начале строки знак `>`
+
+### Ссылки
+
+* Ссылки на внешние ресурсы
+
+    ```
+    [Текст ссылки](http://example.com/ "Необязательный заголовок ссылки")
+    ```
+
+* Ссылки внутри документа
+
+    ```
+    [Текст ссылки](#якорь)
+    ```
+
+    В качестве "якоря" может выступать [заголовок](#Заголовки)
+
+* Ссылки внутри репозитория
+
+    ```
+    [Текст ссылки](относительный путь на файл внутри репозитория)
+    ```
+
+### Таблицы
+
+столбец 1 | столбец 2
+:---:|---:
+1 | sfbvstb
+2 | sbstbn
+
+Как это выглядит в разметке (обратите внимание, содержимое столбцов можно центрировать или выравнивать по правому краю):
+
+```
+столбец 1 | столбец 2
+:---:|---:
+1 | sfbvstb
+2 | sbstbn
+```
+
+### Изображения
+
+Если нужно разместить фотографию, скриншот или сложную схему, то можно добавить изображение
+
+```
+![Alt-текст](http://example.com/ "Заголовок изображения")
+```
+
+> Ссылка может быть и на изображение расположенное в репозитории
+>```
+>![](../img/img.png)
+>```
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Методы программирования](./articles/t2l2.md) | [Содержание](../readme.md#тема-2-языки-и-методы-программирования) | [Основные элементы языка.](./articles/t3l1.md)

+ 121 - 0
articles/skv_lab.md

@@ -0,0 +1,121 @@
+# Создание аккаунта и репозитория в СКВ gihtub
+
+1. Создать аккаунт на https://kolei.ru
+
+    * логин - первая буква имени и фамилия в латинице. Например, логин для "Евгений Колесников" будет **ekolesnikov**
+    * пароль произвольный
+
+2. Создать репозиторий на `kolei.ru` НЕ ДОБАВЛЯЯ В НЕГО README.MD
+
+3. Инициализировать новый репозиторий на компьютере (создать отдельный каталог):
+
+    Вообще в наш курс не входит обучение командной строке операционной системы, но оказалось, что будущие программисты этого не умеют...
+
+    1. Запускаем на локальном компьютере программу `cmd`
+
+        Откроется черное окно консоли, в которой будет написан текущий каталог, для винды это каталого пользователя, обычно это `c:/users/user`
+
+    1. Создаем каталог для проекта командой `mkdir <имя каталога>`, например
+
+        ```
+        mkdir skv
+        ```
+
+    1. Переходим в этот каталог командой `cd <имя каталога>`
+
+        ```
+        cd skv
+        ```
+
+    1. Текущий каталог должен быть примерно таким: `c:/users/user/skv`
+
+    
+
+    Все необходимые команды написаны в репозитории после создания, но надо понимать что они делают и когда их выполнять:
+
+    * Команда `git init` создает в ТЕКУЩЕМ каталоге новый локальный репозиторий (выполняется один раз):
+
+        ```
+        git init
+        ```
+
+    * Прежде чем добавлять файлы в репозиторий нужно создать в корне репозитория файл `.gitignore` и записать туда игнорируемые файлы и каталоги.
+
+        Для `C#` там надо написать:
+
+        ```
+        # в каталогах bin и obj записываются скомпилированные файлы
+        */bin/
+        */obj/
+        # в каталоге .vs хранятся локальные настройки VisualStudio
+        .vs/
+        # в каталоге packages хрянятся зависимости
+        packages/
+        ```
+
+    * Для записи коммитов в репозиторий пользователь должен быть идентифицирован. Для этого выполните команды (один раз для репозитория):
+
+        ```
+        git config user.name "Ваше имя"
+        git config user.email "Ваша почта"
+        ```
+
+        >Если вы работаете дома, то можете один раз выполнить эти команды с флагом `--global`
+
+    * В колледже используется прокси, из-за которого **git** может ругаться на не верный сертификат. В этом случае отключите проверку сертификата:
+
+        ```
+        git config --global http.sslVerify false
+        ```
+
+    * Команда `git add <имя файла>` помечает файл как отслеживаемый системой контроля версий. Вместо конкретного имени можно указать "." (символ точки), чтобы добавить в отслеживаемые все изменившиеся файлы. Выполняется перед каждым коммитом.
+
+        ```
+        git add .
+        ```
+
+    * Команда `git commit` сохраняет отслеживаемые файлы в локальный репозиторий. Выполняется при завершении какого-то законченного атомарного действия (создали класс, написали функцию...)
+
+        ```
+        git commit -m "текст комментария"
+        ```
+
+    * Команда `git remote add <алиас> <url>` добавляет в настройки локального репозитория ссылку на внешний репозиторий. Выполняется один раз для каждого внешнего репозитория (их может быть более одного).
+
+        ```
+        git remote add origin <ссылку на проект копируйте из внешнего репозитория>
+        ```
+
+    * Команда `git push [-u] <алиас внешнего репозитория> <название ветки>` отправляет содержимое локального репозитория в указанный внешний репозиторий. Ветка по-умолчанию в **Git**-е: *master*. Ключ `-u` задает комбинацию алиас + внешний репозиторий по-умолчанию, т.е. в следующий раз можно использовать просто каоманду `git push`.
+
+        ```
+        git push origin master
+        ``` 
+
+    * если на компьютере установлен **credential manager**, то он может запоминать последнего пользователя и не разрешить запись в другой репозиторий. В этом случае удаляем его из системы:
+
+        ```
+        git config --system --unset credential.helper
+        ```
+
+    * для **GOGS** (что-то с буфером передачи)
+
+        ```
+        git config --global http.postBuffer 157286400    
+        ```
+
+4. Исследовать команды, описанные в [лекции](./skv.md#базовые-команды-git). Команды и возвращаемый результат записать в `readme.md`, используя формат **MarkDown**.
+
+    Команды, которые надо выполнить и сохранить логи работы помечены значком `🕮`
+
+    * куски кода или консольный ввод/вывод в MarkDown-е обозначаются тройными обратными кавычками:
+
+        \`\`\`
+        ```    
+        Здесь кусок кода
+        ```    
+        \`\`\`
+
+6. Результаты опубликовать в репозитории, созданном в п.2 и скинуть ссылку в чат.
+
+[Пример выполнения](./skv_lab_example.md)

+ 43 - 0
articles/skv_lab_example.md

@@ -0,0 +1,43 @@
+# Пример выполнения лабораторной работы СКВ
+
+>Вы можете посмотреть как оформлена разметка **MarkDown** в режиме "исходного текста"
+>
+>![](../img/skv01.png)
+
+1. Инициализируем репозиторий командой `git init`
+
+    Результат выполнения команды 
+
+    ```
+    ~/temp/йй$ git init
+    hint: Using 'master' as the name for the initial branch. This default branch name
+    hint: is subject to change. To configure the initial branch name to use in all
+    hint: of your new repositories, which will suppress this warning, call:
+    hint:
+    hint:   git config --global init.defaultBranch <name>
+    hint:
+    hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
+    hint: 'development'. The just-created branch can be renamed via this command:
+    hint:
+    hint:   git branch -m <name>
+    Инициализирован пустой репозиторий Git в /home/kei/temp/йй/.git/
+    ```
+
+1. Создаем любой файл
+
+1. Выполняем команду `git add` (в консоли ничего не выводит)
+
+1. Выполняем команду `git status`
+
+    ```
+    ~/temp/йй$ git status
+    Текущая ветка: master
+
+    Еще нет коммитов
+
+    Изменения, которые будут включены в коммит:
+    (используйте «git rm --cached <файл>...», чтобы убрать из индекса)
+            новый файл:    readme.md
+    ```
+
+... и так все основные комады (до веток)

+ 448 - 0
articles/t1l1.md

@@ -0,0 +1,448 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+&nbsp; | [Содержание](../readme.md#тема-1-основные-принципы-алгоритмизации-и-программирования) | [Основные алгоритмические конструкции](./t1l2.md)
+
+# Основные понятия алгоритмизации
+
+Работа по решению любой задачи с использованием компьютера делится на следующие этапы:
+
+1. Постановка задачи.
+2. Формализация задачи.
+3. Построение алгоритма.
+4. Составление программы на языке программирования.
+5. Отладка и тестирование программы.
+6. Использование программы.
+
+Часто эту последовательность называют технологической цепочкой решения задачи. Непосредственно к программированию в этом списке относятся пункты `3`, `4`, `5`. 
+
+На этапе **постановки задачи** должно быть четко сформулировано, что дано и что требуется найти. Здесь очень важно определить полный набор исходных данных, необходимых для получения решения.
+
+Второй этап — **формализация задачи**. Здесь чаще всего задача переводится на язык математических формул, уравнений, отношений. Если решение требует математического описания какого-то реального объекта, явления или процесса, то формализация равносильна получению соответствующей математической модели.
+
+Третий этап — **построение алгоритма**. Опытные программисты часто сразу пишут программы на языках, не прибегая к каким-либо специальным способам описания алгоритмов (блок-схемам, псевдокодам). Однако в учебных целях полезно использовать эти средства, а затем переводить полученный алгоритм на язык программирования.
+
+Первые три этапа предусматривают работу без компьютера. Дальше следует собственно программирование на определенном языке, в определенной системе программирования. Последний (шестой) этап — это использование уже разработанной программы в практических целях.
+
+Таким образом, программист должен обладать следующими знаниями и навыками:
+
+* уметь строить алгоритмы;
+* знать языки программирования;
+* уметь работать в соответствующей системе программирования.
+ 
+## Понятие алгоритма
+
+Одним из фундаментальных понятий в информатике является понятие алгоритма. Происхождение самого термина «алгоритм» связано с математикой. Это слово происходит от Algorithmi — латинского написания имени Мухаммеда альХорезми (787 — 850), выдающегося математика средневекового Востока. В XII в. был выполнен латинский перевод его математического трактата, из которого европейцы узнали о десятичной позиционной системе счисления и правилах арифметики многозначных чисел. Именно эти правила в то время называли алгоритмами. Сложение, вычитание, умножение столбиком, деление уголком многозначных чисел — вот первые алгоритмы в математике.
+
+В наше время понятие алгоритма трактуется шире. ***Алгоритм — это последовательность команд управления каким-либо исполнителем.***
+
+Алгоритм может быть предназначен для выполнения его человеком или автоматическим устройством — формальным исполнителем. Задача исполнителя — точная реализация уже имеющегося алгоритма. Формальный исполнитель не обязан вникать в сущность алгоритма, а возможно, и неспособен его понять.
+
+Примером формального исполнителя может служить автоматическая стиральная машина, которая неукоснительно исполняет предписанные ей действия, даже если вы забыли положить в нее порошок. Человек тоже может выступать в роли формального исполнителя, но в первую очередь формальными исполнителями являются различные автоматические устройства, и компьютер в том числе.
+
+В разделе информатики под названием **Программирование** изучаются методы программного управления работой ЭВМ. Следовательно, в качестве исполнителя выступает компьютер. 
+
+Компьютер работает с величинами — различными информационными объектами: числами, символами, кодами и т.п. Поэтому алгоритмы, предназначенные для управления компьютером, принято называть *алгоритмами работы с величинами*.
+
+**Данные и величины**. ***Совокупность величин, с которыми работает компьютер, принято называть данными***. По отношению к программе данные делятся на исходные, результаты (окончательные данные) и промежуточные, которые получаются в процессе вычислений.
+
+![](../img/t1l1p10.png)
+
+Например, при решении квадратного уравнения **ах<sup>2</sup> + Ьх + с = 0**
+исходными данными являются коэффициенты `а`, `Ь`, `с`; результатами — корни уравнения `х1`, `х2`; промежуточным данным — дискриминант уравнения **D = b<sup>2</sup> - 4ас**.
+
+Для успешного освоения программирования необходимо усвоить следующее правило: _всякая величина занимает свое определенное место в памяти ЭВМ_ (иногда говорят — ячейку памяти). Хотя термин «ячейка» с точки зрения архитектуры современных ЭВМ несколько устарел, однако в учебных целях его удобно использовать.
+
+У всякой величины имеются ***три основных свойства***: ***имя, значение и тип*** (на самом деле многие современные языки, такие как **Python** или **JavaScript**, обходятся без явного указания типа, интерпретируя тип переменной в зависимости от контекста операции). На уровне команд процессора величина идентифицируется при помощи адреса ячейки памяти, в которой она хранится. В алгоритмах и языках программирования величины делятся на *константы и переменные*. Константа — неизменная величина, и в алгоритме она представляется собственным значением, например: `15`, `34.7`, `k`, `true` и т.д. Переменные величины могут изменять свои значения в ходе выполнения программы и представляются символическими именами — идентификаторами, например: `X`, `S2`, `cod15`. Любая константа, как и переменная, занимает ячейку памяти, а значение этих величин определяется двоичным кодом в этой ячейке.
+
+### Основные типы данных
+
+Теперь о типах величин — *типах данных*. С понятием типа данных вы уже, возможно, встречались, изучая в курсе информатики базы данных и электронные таблицы. Это понятие является фундаментальным для программирования.
+
+В каждом языке программирования существует своя концепция типов данных, своя система типов. Тем не менее в любой язык входит ***минимально необходимый набор основных типов данных***, к которому относятся: ***целый, вещественный, логический и символьный*** типы. С типом величины связаны три характеристики: множество допустимых значений, множество допустимых операций, форма внутреннего представления. Ниже представлены эти характеристики для основных типов данных.
+
+Тип   | Значения | Операции | Внутреннее представление
+------|----------|----------|-------------------------
+Целый | Целые положительные и отрицательные числа в некотором диапазоне.<br/> Примеры: `23`, `-12`, `387` | Арифметические операции с целыми числами: `+`, `-`, `*`,  целое деление и остаток от деления.<br/>Операции отношений (`<`, `>`, `=` и др.) | Формат с фиксированной точкой
+Вещественный | Любые (целые и дробные) числа в некотором диапазоне.<br/> Примеры: `2.5`, `-0.01`, `45.0`, `3.6-109` | Арифметические операции: `+`, `-`, `*`, /.<br/>Операции отношений | Формат с плавающей точкой
+Логический | `True` (истина),<br/>`False` (ложь) | Логические операции: И (`&`), ИЛИ (\|), HE (`~`). Операции отношений | 1 бит:<br/>1 - true;<br/>0 - false
+Символьный | Любые символы компьютерного алфавита.<br/>Примеры: 'а', '5', '+', '$' | Операции отношений | Коды таблицы символьной кодировки. 1 символ - 1 байт (Сейчас используются многобайтные кодировки: UTF-8, UTF-16...)
+
+Типы констант определяются по контексту (т.е. по форме записи в тексте), а типы переменных устанавливаются в описаниях переменных (не во всех языках; Python, например, не имеет явного определения типа, тип переменной определяетя при первом присваивании).
+
+Есть еще один вариант классификации данных — классификация по структуре. Данные делятся на *простые и структурированные*. Для простых величин (их еще называют скалярными) справедливо утверждение: одна величина — одно значение, для структурированных: одна величина — множество значений. К структурированным величинам относятся массивы, строки, множества и т.д.
+
+## Свойства алгоритма
+
+**Массовость** — алгоритм решения задачи разрабатывается в общем виде, то есть он должен быть применим для некоторого класса задач, различающихся только исходными данными. При этом исходные данные могут выбираться из некоторой области, которая называется областью применимости алгоритма.
+
+**Понятность** - команды, используемые в алгоритме, должны быть понятны исполнителю.
+
+**Дискретность** (прерывность, раздельность) — алгоритм должен представлять процесс решения задачи как последовательное выполнение простых шагов. Каждое действие, предусмотренное алгоритмом, исполняется только после того, как закончилось исполнение предыдущего.
+
+**Определенность** (детерминнированность) — предполагает получение однозначного результата вычислительного процecca при заданных исходных данных. Благодаря этому свойству процесс выполнения алгоритма носит механический характер.
+
+**Результативность** (конечность) — алгоритм должен приводить к решению задачи за конечное число шагов.
+
+## Формы записи алгоритмов
+
+На практике наиболее распространены следующие формы представления алгоритмов:
+
+- словесная (запись на естественном языке)
+- графическая (изображения из графических символов)
+- псевдокоды (полуформализованные описания алгоритмов на условном алгоритмическом языке, включающие в себя как элементы языка программирования, так и фразы естественного языка, общепринятые математические обозначения и др.
+- программная (тексты на языках программирования)
+
+**Пример:** написать алгоритм "Одеться по погоде". Если на улице температура ниже 0, то необходимо надеть шубу, иначе – куртку.
+
+### Словесный способ записи алгоритма
+
+Словесный способ записи алгоритмов представляет собой описание последовательных этапов обработки данных. Алгоритм задается в произвольном изложении на естественном языке.
+
+```
+Алгоритм ПОГОДА  
+Начало  
+определить температуру воздуха  
+если температура ниже 0, то надеть шубу, иначе надеть куртку  
+Конец.
+```
+
+Словесный способ не имеет широкого распространения, так как такие описания:
+- строго не формализуемы;
+- страдают многословностью записей;
+- допускают неоднозначность толкования отдельных предписаний.
+
+### Графический способ записи алгоритмов
+
+Наибольшее распространение благодаря своей наглядности получил графический способ записи алгоритмов. При графическом представлении алгоритм изображается в виде последовательности связанных между собой функциональных блоков, каждый из которых соответствует выполнению одного или нескольких действий.
+
+Такое графическое представление называется схемой алгоритма или блок-схемой. В блок-схеме каждому типу действий (вводу исходных данных, вычислению значений выражений, проверке условий, управлению повторением действий, окончанию обработки и т.п.) соответствует геометрическая фигура, представленная в виде блочного символа. Блочные символы соединяются линиями переходов, определяющими очередность выполнения действий. В таблице приведены наиболее часто употребляемые символы.
+
+Название символа | Обозначение и пример заполнения | Пояснение
+-----------------|---------------------------------|------------
+Процесс		       | ![](../img/t1l1p1.gif)          |Вычислительное действие или последовательность действий
+Решение          | ![](../img/t1l1p2.gif)		       | Проверка условий
+Модификация	     | ![](../img/t1l1p3.gif)          | Начало цикла
+Предопределенный процесс | ![](../img/t1l1p4.gif)  | Вычисления по подпрограмме, стандартной подпрограмме
+Ввод-вывод       | ![](../img/t1l1p5.gif)          | Ввод-вывод в общем виде
+Пуск-останов     | ![](../img/t1l1p6.gif)          | Начало, конец алгоритма, вход и выход в подпрограмму
+Документ         | ![](../img/t1l1p7.gif)          | Вывод результатов на печать
+
+Блок **процесс** применяется для обозначения действия или последовательности действий,изменяющих значение, форму представления или размещения данных. Для улучшения наглядности схемы несколько отдельных блоков обработки можно объединять в один блок. Представление отдельных операций достаточно свободно.
+
+Блок **решение** используется для обозначения переходов управления по условию. В каждом блоке **решение** должны быть указаны вопрос, условие или сравнение, которые он определяет.
+
+Блок **модификация** используется для организации циклических конструкций. (Слово модификация означает видоизменение, преобразование). Внутри блока записывается параметр цикла, для которого указываются его начальное значение, граничное условие и шаг изменения значения параметра для каждого повторения.
+
+Блок **предопределенный процесс** используется для указания обращений к вспомогательным алгоритмам, существующим автономно в виде некоторых самостоятельных модулей, и для обращений к библиотечным подпрограммам.
+
+Блок **Ввод-вывод** используется для преобразования данных в фор­му, пригодную для обработки (ввод) или отображения результатов обработки (вывод). Отдельным логическим устройствам компьютера или отдельным функциям об­мена соответствуют определенные блочные символы. В каждом из них указыва­ются тип устройства или файла данных, тип информации, участвующий в обме­не, а также вид операции обмена.
+
+Блок **Пуск-останов** используется для обозначения начала, конца, прерывания процесса обработки данных или выполнения программы.
+
+Блок **Документ** предназначен для ввода-вывода данных, носителем которых служит бумага.
+
+![](../img/t1l1p8.gif)
+
+### Псевдокод
+
+Псевдокод представляет собой систему обозначений и правил, предназначенную для единообразной записи алгоритмов.
+
+Псевдокод занимает промежуточное место между естественным и формальным языками. С одной стороны, он близок к обычному естественному языку, поэтому алгоритмы могут на нем записываться и читаться как обычный текст. С другой стороны, в псевдокоде используются некоторые формальные конструкции и математическая символика, что приближает запись алгоритма к общепринятой математической записи.
+
+В псевдокоде не приняты строгие синтаксические правила для записи команд, присущие формальным языкам, что облегчает запись алгоритма на стадии его проектирования и дает возможность использовать более широкий набор команд, рассчитанный на абстрактного исполнителя.
+
+Однако в псевдокоде обычно имеются некоторые конструкции, присущие формальным языкам, что облегчает переход от записи на псевдокоде к записи алгоритма на формальном языке. В частности, в псевдокоде, так же, как и в формальных языках, есть служебные слова, смысл которых определен раз и навсегда. Они выделяются в печатном тексте жирным шрифтом, а в рукописном тексте подчеркиваются.
+
+Единого или формального определения псевдокода не существует, поэтому возможны различные псевдокоды, отличающиеся набором служебных слов и основных (базовых) конструкций.
+
+### Программный способ записи алгоритмов
+
+При записи алгоритма в словесной форме, в виде блок-схемы или на псевдокоде допускается определенный произвол при изображении команд. Вместе с тем такая запись точна настолько, что позволяет человеку понять суть дела и исполнить алгоритм.
+
+Однако на практике в качестве исполнителей алгоритмов используются специальные автоматы - компьютеры. Поэтому алгоритм, предназначенный для исполнения на компьютере, должен быть записан на понятном ему языке. И здесь на первый план выдвигается необходимость точной записи команд, не оставляющей места для произвольного толкования их исполнителем.
+
+Следовательно, язык для записи алгоритмов должен быть формализован. Такой язык принято называть языком программирования, а запись алгоритма на этом языке - программой для компьютера.
+
+Программа, создаваемая человеком - программистом, представляет собой текст, состоящий из знаков, как правило букв, цифр и специальных знаков. Знаки в тексте программы часто объединены в последовательности - ключевые слова, слова объединены в предложения языка программирования - операторы. Каждый оператор, как правило, записывается в отдельную строку текста программы.
+
+Таким образом текстовое программирование представляет собой иерархическую последовательность знаков, слов, операторов, записываемых и читаемых последовательно, как обычный текст человеческой письменности.
+
+```cs
+//Пример программы на языке C#
+namespace oap
+{
+  class Program
+  {
+    static void Main(string[] args)
+    {
+      Console.WriteLine("введите температуру воздуха t: ");
+      var t = int.Parse( Console.ReadLine() );
+      if (t < 0)
+        Console.WriteLine("одеть шубу");
+      else
+        Console.WriteLine("одеть куртку");
+    }
+  }
+}
+```
+
+### Структурное программирование
+
+Запись алгоритмов решения сложных задач в любой форме, в том числе в виде блок-схемы, может быть слишком объемной и сложной. Поэтому на практике используют некоторые методы, облегчающие построение и реализацию алгоритмов.
+
+Одним из наиболее распространенных является ***метод структурного программирования***, или ***конструирование алгоритмов методом последовательной детализации***. При пошаговой детализации алгоритмы записываются в виде множества вспомогательных алгоритмов, решающих вспомогательные подзадачи, а каждая из них требует получения определенных промежуточных результатов.
+
+Разработав основной алгоритм, можно приступить к разработке алгоритмов «второго уровня», которые, в свою очередь, могут требовать дальнейшей детализации. Процесс детализации продолжается до тех пор, пока не будут написаны все нужные вспомогательные алгоритмы. Таким образом, основной алгоритм представляет собой план действий, которые необходимо выполнить для достижения поставленной цели, а суть каждого действия расшифровывается в соответствующем вспомогательном алгоритме.
+
+Каждый вспомогательный алгоритм описывает способ решения некоторой вспомогательной задачи или даже общий способ решения некоторого класса вспомогательных подзадач.
+
+Для реализации вспомогательных алгоритмов служат подпрограммы, или процедуры. Подпрограмма — часть алгоритма (программы), оформленная в виде, допускающем многократное обращение к ней из разных точек программы. Обращение к подпрограмме — переход к выполнению подпрограммы с заданием информации, необходимой для ее выполнения и возврата.
+
+## Общие принципы построения алгоритмов
+
+При разработке алгоритма используют следующие основные принципы.
+
+Принцип поэтапной детализации алгоритма (другое название — "проектирование сверху-вниз"). Этот принцип предполагает первоначальную разработку алгоритма в виде укрупненных блоков (разбиение задачи на подзадачи) и их постепенную детализацию.
+
+Принцип "от главного к второстепенному", предполагающий составление алгоритма, начиная с главной конструкции. При этом, часто, приходится "достраивать" алгоритм в обратную сторону, например, от середины к началу.
+
+Принцип структурирования, т.е. использования только типовых алгоритмических структур при построении алгоритма. Нетиповой структурой считается, например, циклическая конструкция, содержащая в теле цикла дополнительные выходы из цикла. В программировании нетиповые структуры появляются в результате злоупотребления командой безусловного перехода (GoTo). При этом программа хуже читается и труднее отлаживается.
+
+## Определение сложности работы алгоритмов
+
+Существует несколько способов измерения сложности алгоритма. Программисты обычно сосредотачивают внимание на скорости алгоритма, но не менее важны и другие показатели – требования к объёму памяти, свободному месте на диске. Использование быстрого алгоритма не приведёт к ожидаемым результатам, если для его работы понадобится больше памяти, чем есть у компьютера.
+
+### Память или время
+
+Многие алгоритмы предлагают выбор между объёмом памяти и скоростью. Задачу можно решить быстро, использую большой объём памяти, или медленнее, занимая меньший объём.
+Типичным примером в данном случае служит алгоритм поиска кратчайшего пути. Представив карту города в виде сети, можно написать алгоритм для определения кратчайшего расстояния между двумя любыми точками этой сети. Чтобы не вычислять эти расстояния всякий раз, когда они нам нужны, мы можем вывести кратчайшие расстояния между всеми точками и сохранить результаты в таблице. Когда нам понадобится узнать кратчайшее расстояние между двумя заданными точками, мы можем просто взять готовое расстояние из таблицы.  
+
+Результат будет получен мгновенно, но это потребует огромного объёма памяти. Карта большого города может содержать десятки тысяч точек. Тогда, описанная выше таблица, должна содержать более 10 млрд. ячеек. Т.е. для того, чтобы повысить быстродействие алгоритма, необходимо использовать дополнительные 10 Гб памяти.  
+
+Из этой зависимости проистекает идея объёмно-временной сложности. При таком подходе алгоритм оценивается, как с точки зрении скорости выполнения, так и с точки зрения потреблённой памяти.
+
+Мы будем уделять основное внимание временной сложности, но, тем не менее, обязательно будем оговаривать и объём потребляемой памяти.
+
+### Оценка порядка
+
+При сравнении различных алгоритмов важно знать, как их сложность зависит от объёма входных данных. Допустим, при сортировке одним методом обработка тысячи чисел занимает 1 с., а обработка миллиона чисел – 10 с., при использовании другого алгоритма может потребоваться 2 с. и 5 с. соответственно. В таких условиях нельзя однозначно сказать, какой алгоритм лучше.
+
+В общем случае сложность алгоритма можно оценить по порядку величины. Алгоритм имеет сложность `O(f(n))`, если при увеличении размерности входных данных N, время выполнения алгоритма возрастает с той же скоростью, что и функция `f(N)`. Рассмотрим код, который для матрицы `A[NxN]` находит максимальный элемент в каждой строке.
+
+```pas
+for i:=1 to N do
+begin
+  max:=A[i,1];
+  for j:=1 to N do
+  begin
+    if A[i,j]>max then
+      max:=A[i,j]
+  end;
+  writeln(max);
+end;
+```
+
+В этом алгоритме переменная `i` меняется от `1` до `N`. При каждом изменении `i`, переменная `j` тоже меняется от `1` до `N`. Во время каждой из `N` итераций внешнего цикла, внутренний цикл тоже выполняется `N` раз. Общее количество итераций внутреннего цикла равно `N*N`. Это определяет сложность алгоритма **O(N<sup>2</sup>)**.
+
+Оценивая порядок сложности алгоритма, необходимо использовать только ту часть, которая возрастает быстрее всего. Предположим, что рабочий цикл описывается выражением **N<sup>3</sup> + N**. В таком случае его сложность будет равна **O(N<sup>3</sup>)**. Рассмотрение быстро растущей части функции позволяет оценить поведение алгоритма при увеличении `N`. Например, при `N=100`, разница между **N<sup>3</sup> + N = 1000100** и **N = 1000000** равна всего лишь `100`, что составляет 0,01%.
+
+При вычислении `O` можно не учитывать постоянные множители в выражениях. Алгоритм с рабочим шагом **3N<sup>3</sup>** рассматривается, как **O(N<sup>3</sup>)**. Это делает зависимость отношения **O(N)** от изменения размера задачи более очевидной.
+
+### Определение сложности
+
+Наиболее сложными частями программы обычно является выполнение циклов и вызов процедур. В предыдущем примере весь алгоритм выполнен с помощью двух циклов.
+
+Если одна процедура вызывает другую, то необходимо более тщательно оценить сложность последней. Если в ней выполняется определённое число инструкций (например, вывод на печать),то на оценку сложности это практически не влияет. Если же в вызываемой процедуре выполняется **O(N)** шагов, то функция может значительно усложнить алгоритм. Если же процедура вызывается внутри цикла, то влияние может быть намного больше.
+
+В качестве примера рассмотрим две процедуры: **Slow** со сложностью **O(N<sup>3</sup>)** и **Fast** со сложностью **O(N<sup>2</sup>)**.
+
+```pas
+procedure Slow;
+var
+  i,j,k: integer;
+begin
+  for i:=1 to N do
+    for j:=1 to N do
+      for k:=1 to N do
+        //какое-то действие
+end;
+
+procedure Fast;
+var
+  i,j: integer;
+begin
+  for i:=1 to N do
+    for j:=1 to N do
+      Slow;
+end;
+
+procedure Both;
+begin
+  Fast;
+end;
+```
+
+Если во внутренних циклах процедуры **Fast** происходит вызов процедуры **Slow**, то сложности процедур перемножаются. В данном случае сложность алгоритма составляет **O(N<sup>2</sup>)*O(N<sup>3</sup>)=O(N<sup>5</sup>)**.
+
+Если же основная программа вызывает процедуры по очереди, то их сложности складываются: 
+
+**O(N<sup>2</sup>)+O(N<sup>3</sup>)=O(N<sup>3</sup>)**. 
+
+Следующий фрагмент имеет именно такую сложность:
+
+```pas
+procedure Slow;
+var
+  i,j,k: integer;
+begin
+  for i:=1 to N do
+    for j:=1 to N do
+      for k:=1 to N do
+        {какое-то действие}
+end;
+
+procedure Fast;
+var
+  i,j: integer;
+begin
+  for i:=1 to N do
+    for j:=1 to N do
+      {какое-то действие}
+end;
+
+procedure Both;
+begin
+  Fast;
+  Slow;
+end;
+```
+
+### Сложность рекурсивных алгоритмов
+
+#### Простая рекурсия
+
+Рекурсивными процедурами называются процедуры, которые вызывают сами себя. Их сложность определить довольно тяжело. Сложность этих алгоритмов зависит не только от сложности внутренних циклов, но и от количества итераций рекурсии. Рекурсивная процедура может выглядеть достаточно простой, но она может серьёзно усложнить программу, многократно вызывая себя.
+
+Рассмотрим рекурсивную реализацию вычисления факториала:
+
+```pas
+function Factorial(n: Word): integer;
+begin
+  if n > 1 then
+    Factorial:=n*Factorial(n-1)
+  else
+    Factorial:=1;
+end;
+```
+
+Эта процедура выполняется `N` раз, таким образом, вычислительная сложность этого алгоритма равна **O(N)**.
+
+#### Многократная рекурсия
+
+Рекурсивный алгоритм, который вызывает себя несколько раз, называется многократной рекурсией. Такие процедуры гораздо сложнее анализировать, кроме того, они могут сделать алгоритм гораздо сложнее.
+
+Рассмотрим такую процедуру:
+
+```pas
+procedure DoubleRecursive(N: integer);
+begin
+  if N>0 then
+  begin
+    DoubleRecursive(N-1);
+    DoubleRecursive(N-1);
+  end;
+end;
+```
+
+Поскольку процедура вызывается дважды, можно было бы предположить, что её рабочий цикл будет равен O(2N)=O(N). Но на самом деле ситуация гораздо сложнее. Если внимательно исследовать этот алгоритм, то станет очевидно, что его сложность равна **O(2<sup>(N+1)</sup>-1)=O(2<sup>N</sup>)**. 
+
+Всегда надо помнить, что анализ сложности рекурсивных алгоритмов весьма нетривиальная задача.
+
+#### Объёмная сложность рекурсивных алгоритмов
+
+Для всех рекурсивных алгоритмов очень важно понятие объёмной сложности. При каждом вызове процедура запрашивает небольшой объём памяти, но этот объём может значительно увеличиваться в процессе рекурсивных вызовов. По этой причине всегда необходимо проводить хотя бы поверхностный анализ объёмной сложности рекурсивных процедур.
+
+### Средний и наихудший случай
+
+Оценка сложности алгоритма до порядка является верхней границей сложности алгоритмов. Если программа имеет большой порядок сложности, это вовсе не означает, что алгоритм будет выполняться действительно долго. На некоторых наборах данных выполнение алгоритма занимает намного меньше времени, чем можно предположить на основе их сложности. Например, рассмотрим код, который ищет заданный элемент в векторе A.
+
+```pas
+function Locate(data: integer): integer;
+var
+  i: integer;
+  fl: boolean;
+begin
+  fl:=false; i:=1;
+  while (not fl) and (i<=N) do
+  begin
+    if A[i]=data then
+      fl:=true
+    else
+      i:=i+1;
+  end;
+  if not fl then
+    i:=0;
+  Locate:=i;
+end;
+```
+
+Если искомый элемент находится в конце списка, то программе придётся выполнить N шагов. В таком случае сложность алгоритма составит **O(N)**. В этом наихудшем случае время работы алгоритма будем максимальным.
+
+С другой стороны, искомый элемент может находится в списке на первой позиции. Алгоритму придётся сделать всего один шаг. Такой случай называется наилучшим и его сложность можно оценить, как **O(1)**.
+
+Оба эти случая маловероятны. Нас больше всего интересует ожидаемый вариант. Если элемента списка изначально беспорядочно смешаны, то искомый элемент может оказаться в любом месте списка. В среднем потребуется сделать `N/2` сравнений, чтобы найти требуемый элемент. Значит сложность этого алгоритма в среднем составляет **O(N/2)=O(N)**.
+
+В данном случае средняя и ожидаемая сложность совпадают, но для многих алгоритмов наихудший случай сильно отличается от ожидаемого. Например, алгоритм быстрой сортировки в наихудшем случае имеет сложность порядка **O(N<sup>2</sup>)**, в то время как ожидаемое поведение описывается оценкой **O(N*log(N))**, что много быстрее.
+
+### Общие функции оценки сложности
+
+Сейчас мы перечислим некоторые функции, которые чаще всего используются для вычисления сложности. Функции перечислены в порядке возрастания сложности. Чем выше в этом списке находится функция, тем быстрее будет выполняться алгоритм с такой оценкой.
+
+1. **C** – константа (время выполнения алгоритма не зависит от входных параметров, линейные алгоритмы)
+2. **log(log(N))**
+3. **log(N)** - (поиск в сортированном массиве)
+4. **N<sup>C</sup>**, 0<C<1
+5. **N** - линейная сложность (поиск в не сортированном массиве)
+6. **N*log(N)**
+7. **N<sup>C</sup>**, C>1
+8. **C<sup>N</sup>**, C>1
+9. **N!**
+
+Если мы хотим оценить сложность алгоритма, уравнение сложности которого содержит несколько этих функций, то уравнение можно сократить до функции, расположенной ниже в таблице. Например, **O(log(N)+N!)=O(N!)**.
+
+Если алгоритм вызывается редко и для небольших объёмов данных, то приемлемой можно считать сложность **O(N<sup>2</sup>)**, если же алгоритм работает в реальном времени, то не всегда достаточно производительности **O(N)**.
+
+Обычно алгоритмы со сложностью **N*log(N)** работают с хорошей скоростью. Алгоритмы со сложностью **N<sup>C</sup>** можно использовать только при небольших значениях `C`. Вычислительная сложность алгоритмов, порядок которых определяется функциями **C<sup>N</sup>** и **N!** очень велика, поэтому такие алгоритмы могут использоваться только для обработки небольшого объёма данных.
+
+В заключение приведём таблицу, которая показывает, как долго компьютер, осуществляющий миллион операций в секунду, будет выполнять некоторые медленные алгоритмы.
+
+Сложность    | N=10    |   N=20  | N=30    | N=40           | N=50
+:-----------:|:-------:|:-------:|:-------:|:--------------:|:-:
+ N<sup>3     | 0.001 c | 0.008 c | 0.027 c | 0.064 c        | 0.125 c
+ 2<sup>N     | 0.001 c | 1.05 c  | 17.9 мин|1.29 дней       |35.7 лет
+ 3<sup>N     | 0.059 c | 58.1 мин| 6.53 лет| 3.86\*10<sup>5</sup> лет | 2.28\*10<sup>10</sup> лет
+ N!          | 3.63 c  | 7.71\*10<sup>4</sup> лет | 8.41\*10<sup>18</sup> лет | 2.59\*10<sup>34</sup> лет | 9.64\*10<sup>50</sup> лет
+
+[Википедия: Временная сложность алгоритма](https://ru.wikipedia.org/wiki/%D0%92%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%B0%D1%8F_%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D0%B0)
+
+---
+
+КОНТРОЛЬНЫЕ ВОПРОСЫ
+
+1. [Этапы решения задачи на компьютере? Охарактеризуйте их. Проиллюстрируйте этапы постановки и формализации на примере задачи: вычислить время движения моторной лодки между двумя пунктами.](#основные-понятия-алгоритмизации)
+
+2. [Понятие алгоритма.](#понятие-алгоритма)
+
+3. [Основные типы данных.](#основные-типы-данных)
+
+4. [Свойства алгоритма.](#свойства-алгоритма)
+
+5. [Формы записи алгоритмов.](#формы-записи-алгоритмов)
+
+6. [Что такое структурное программирование? Каковы основные прнципы структурной методики построения алгоритмов? ](#структурное-программирование)
+
+---
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:------:|:----------------:
+&nbsp; | [Содержание](../readme.md#тема-1-основные-принципы-алгоритмизации-и-программирования) | [Основные алгоритмические конструкции](./t1l2.md)

+ 424 - 0
articles/t1l2.md

@@ -0,0 +1,424 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Основные понятия алгоритмизации](./t1l1.md) | [Содержание](../readme.md#тема-1-основные-принципы-алгоритмизации-и-программирования) | [Логические основы алгоритмизации](./t1l3.md)
+
+# Основные алгоритмические конструкции
+
+Алгоритм применительно к вычислительной машине — точное предписание, т.е. набор операций и правил их чередования, при помощи которого, начиная с некоторых исходных данных, можно решить любую задачу фиксированного типа.
+
+Алгоритмы в зависимости от цели, начальных условий задачи, путей ее решения, определения действий исполнителя подразделяются следующим образом:
+
+## Линейный алгоритм
+
+**Линейный алгоритм** — набор команд (указаний), выполняемых последовательно друг за другом.
+
+Основным элементарным действием в линейных алгоритмах является присваивание значения переменной величине. Если значение константы определено видом ее записи, то переменная величина получает конкретное значение только в результате присваивания. Присваивание может осуществляться двумя способами: с помощью команды присваивания и с помощью команды ввода.
+
+Рассмотрим пример. В школьном учебнике математики правила деления обыкновенных дробей описаны так:
+
+1. Числитель первой дроби умножить на знаменатель второй дроби.
+2. Знаменатель первой дроби умножить на числитель второй дроби.
+3. Записать дробь, числитель которой есть результат выполнения пункта 1, а знаменатель — результат выполнения пункта 2.
+
+В алгебраической форме это выглядит следующим образом:
+
+![](../img/t1l2p1.png)
+
+Построим алгоритм деления дробей для ЭВМ. В этом алгоритме сохраним те же обозначения для переменных, которые использованы в записанной выше формуле. Исходными данными являются целочисленные переменные а, Ь, с, d. Результатом — также целые величины m и n. Блок-схема и текст алгоритма на языке программирования (ЯП) Kotlin приведены ниже.
+
+![](../img/t1l2p2.png)
+
+```cs
+namespace oap
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            Console.WriteLine("Enter a, b, c, d: ");
+            var a = int.Parse(Console.ReadLine());
+            var b = int.Parse(Console.ReadLine());
+            var c = int.Parse(Console.ReadLine());
+            var d = int.Parse(Console.ReadLine());
+            var m = a * d;
+            var n = b * c;
+            Console.WriteLine($"m={m}, n={n}");
+        }
+    }
+}
+```
+
+Формат команды присваивания следующий:
+
+```
+переменная = выражение  
+```    
+
+Знак «=» нужно читать как «присвоить».  
+
+Команда присваивания обозначает следующие действия, выполняемые компьютером:
+
+1. Вычисляется выражение.
+2. Полученное значение присваивается переменной.
+
+В приведенном выше алгоритме присутствуют две команды присваивания. В блок-схемах команда присваивания записывается в прямоугольнике. Такой блок называется вычислительным блоком.
+
+В описаниях алгоритмов необязательно соблюдать строгие правила в записи выражений. Их можно писать в обычной математической форме. Это еще не язык программирования со строгим синтаксисом.
+
+В приведенном алгоритме присутствуют команды ввода:
+
+```cs
+var a = int.Parse(Console.ReadLine());
+var b = int.Parse(Console.ReadLine());
+var c = int.Parse(Console.ReadLine());
+var d = int.Parse(Console.ReadLine());
+```
+
+В блок-схеме команда ввода записывается в параллелограмме — блоке ввода-вывода. При выполнении данной команды процессор прерывает работу и ожидает действий пользователя. Пользователь должен набрать на устройстве ввода (клавиатуре) значения вводимых переменных и нажать на клавишу ввода Enter. Обычно с помощью команды ввода присваиваются значения исходных данных, а команда присваивания используется для получения промежуточных и конечных величин.
+
+Полученные компьютером результаты решения задачи должны быть сообщены пользователю. Для этих целей предназначена команда вывода:
+
+```cs
+Console.WriteLine($"m={m}, n={n}");
+```
+
+С помощью этой команды результаты выводятся на экран или на устройство печати на бумагу.
+
+Поскольку присваивание является важнейшей операцией в вычислительных алгоритмах, обсудим ее более подробно.
+
+Рассмотрим последовательное выполнение четырех команд присваивания, в которых участвуют две переменные величины a и b.
+
+В приведенной ниже таблице напротив каждой команды присваивания указываются значения переменных, которые устанавливаются после ее выполнения.
+
+Команда | a | b
+--------|---|---
+a=1     | 1 | -
+b=a*2   | 1 | 2
+a=b     | 2 | 2
+b=a+b   | 2 | 4
+
+
+Этот пример иллюстрирует три основных свойства команды присваивания:
+
+* пока переменной не присвоено значение, она остается неопределенной;
+* значение, присвоенное переменной, сохраняется в ней вплоть до выполнения следующей команды присваивания этой переменной;
+* новое значение, присваиваемое переменной, заменяет ее предыдущее значение.
+
+Рассмотрим один очень полезный алгоритм, который приходится часто использовать при программировании. Даны две величины: Х и Y. Требуется произвести между ними обмен значениями. Например, если первоначально было Х=1, Y=2, то после обмена должно стать: Х=2, Y=1.
+
+Хорошей моделью для решения этой задачи является следующая ситуация: имеются два стакана — один с молоком, другой с водой. Требуется произвести обмен их содержимым. Всякому ясно, что в этом случае нужен дополнительный третий пустой стакан. Последовательность действий будет следующей: 1) перелить из первого стакана в третий; 2) перелить из второго в первый;
+3) перелить из третьего во второй. Цель достигнута!
+
+По аналогии для обмена значениями двух переменных нужна третья дополнительная переменная. Назовем ее Z. Тогда задача обмена решается последовательным выполнением трех команд присваивания:
+
+Команда   | X | Y | Z
+----------|---|---|---
+ввод X, Y | 1 | 2 | -
+Z = X     | 1 | 2 | 1
+X = Y     | 2 | 2 | 1
+Y = Z     | 2 | 1 | 1
+
+Аналогия со стаканами не совсем точна в том смысле, что при переливании из одного стакана в другой первый становится пустым. В результате же присваивания (Х = Y) переменная, стоящая справа (Y), сохраняет свое значение.
+
+Алгоритм для деления дробей имеет линейную структуру. В нем все команды выполняются в строго однозначной последовательности, каждая по одному разу. Линейный алгоритм составляется из команд присваивания, ввода, вывода и обращения к вспомогательным алгоритмам (об этом позже).
+
+При описании алгоритмов в блок-схемах типы, как правило, не указываются (но подразумеваются). В алгоритмах для всех переменных типы указываются явно. В них используются следующие обозначения типов: Int — целый тип, Float — вещественный тип, String — символьный (литерный) тип, Boolean — логический тип. В алгоритме для деления дробей для всех переменных указан тип Int.
+
+## Разветвляющийся алгоритм
+
+**Разветвляющийся алгоритм** — алгоритм, содержащий хотя бы одно условие, в результате проверки которого ЭВМ обеспечивает переход на один из двух возможных шагов.
+
+## Циклический алгоритм
+
+**Циклический алгоритм** — алгоритм, предусматривающий многократное повторение одного и того же действия (одних и тех же операций) над новыми исходными данными. К циклическим алгоритмам сводится большинство методов вычислений, перебора вариантов. Цикл программы — последовательность команд (серия, тело цикла), которая может выполняться многократно (для новых исходных данных) до удовлетворения некоторому условию.
+
+Составим алгоритм решения квадратного уравнения: ***ax<sup>2</sup>+bx+c=0***
+
+Задача хорошо знакома из математики. Исходными данными здесь являются коэффициенты а, b, с. Решением в общем случае будут два корня х1 и х2, которые вычисляются по формуле:
+
+![](../img/t1l2p3.png)
+
+```cs
+namespace oap
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            Console.WriteLine("Введите a, b, c: ");
+            var a = int.Parse(Console.ReadLine());
+            var b = int.Parse(Console.ReadLine());
+            var c = int.Parse(Console.ReadLine());
+            var d = b * b - 4 * a * c;
+            var x1 = (-b + Math.Sqrt(d)) / (2 * a);
+            var x2 = (-b - Math.Sqrt(d)) / (2 * a);
+            Console.WriteLine($"x1={x1}, x2={x2}");
+        }
+    }
+}
+```
+
+```
+Введите a, b, c:
+3
+2
+1
+x1=NaN, x2=NaN
+```
+
+Слабость такого алгоритма видна невооруженным глазом. Он не обладает важнейшим свойством, предъявляемым к качественным алгоритмам, — универсальностью по отношению к исходным данным. *Какими бы ни были значения исходных данных, алгоритм должен приводить к определенному результату и завершать работу.* Результатом может быть число, но может быть и сообщение о том, что при определенных данных задача решения не имеет. Недопустимы остановки в середине алгоритма из-за невозможности выполнить какую-то операцию. Упомянутое свойство называют результативностью алгоритма (в любом случае должен быть получен какой-то результат). 
+
+Чтобы построить универсальный алгоритм, сначала требуется тщательно проанализировать математическое содержание задачи. 
+
+Решение уравнения зависит от значений коэффициентов а, b, с. Вот анализ рассмотренной выше задачи (ограничиваемся только поиском вещественных корней):
+
+если а = 0, b = 0, с = 0, то любое х — решение уравнения;  
+если а = 0, b = 0, с <> О, то уравнение действительных решений не имеет;  
+если а = 0, b <> О, то это линейное уравнение, которое имеет одно решение х = -c/b;  
+если а<>0 и d=b<sup>2</sup>-4ac >= 0, то уравнение имеет два вещественных корня (формулы приведены выше);  
+если a<>0 и d<0, то уравнение не имеет вещественных корней.
+
+![](../img/t1l2p4.png)
+
+Этот же алгоритм на Kotlin: 
+
+```kt
+fun main(){
+    println("Введите a, b, c:")
+    val a = readLine()!!.toInt()
+    val b = readLine()!!.toInt()
+    val c = readLine()!!.toInt()
+
+    var x1: Float
+
+    if(a==0){
+        if(b==0){
+            if(c==0) println("любое X")
+            else println("нет решений")
+        } else {
+            x1 = -c.toFloat()/b
+            println("X=$x1")
+        }
+    } else {
+        val d = b*b-4*a*c
+        if(d<0) println("нет вещественных корней")
+        else{
+            x1 = (-b+sqrt(d.toFloat()))/(2*a)
+            val x2 = (-b-sqrt(d.toFloat()))/(2*a)
+            println("x1=$x1, x2=$x2")
+        }
+    }
+}
+```
+
+В этом алгоритме многократно использована *структурная команда ветвления*. Общий вид команды ветвления в блок-схемах и на ЯП следующий: 
+
+![](../img/t1l2p5.png)
+
+```cs
+if (условие) {серия1}
+else {серия2}
+```
+
+Вначале проверяется условие (вычисляется отношение, логическое выражение). Если условие истинно, то выполняется серия 1 — последовательность команд, на которую указывает стрелка с надписью «да» (положительная ветвь). В противном случае выполняется серия 2 (отрицательная ветвь). В языке Kotlin условие записывается после служебного слова if, положительная ветвь — сразу после условия, отрицательная — после слова else.
+
+Если на ветвях одного ветвления содержатся другие ветвления, то такой алгоритм имеет структуру *вложенных ветвлений*. Именно такую структуру имеет алгоритм «Корни квадратного уравнения».
+
+Рассмотрим следующую задачу: дано целое положительное число n. Требуется вычислить n! (n-факториал). Вспомним определение факториала: 
+
+![](../img/t1l2p6.png)
+
+Ниже приведена блок-схема алгоритма. В нем используются три переменные целого типа: n — аргумент; i — промежуточная переменная; F — результат. Для проверки правильности алгоритма построена трассировочная таблица. В такой таблице для конкретных значений исходных данных по шагам прослеживается изменение переменных, входящих в алгоритм. Данная таблица составлена для случая п = 3. 
+
+![](../img/t1l2p7.png)
+
+Шаг | n |  F  | i | Условие
+:--:|:-:|:---:|:-:|---------
+1   | 3 |     |   | 
+2   |   | 1   |   |    
+3   |   |     | 1 |    
+4   |   |     |   | 1<=3, да    
+5   |   | 1   |   |    
+6   |   |     | 2 |    
+7   |   |     |   | 2<=3, да
+8   |   | 2   |   |    
+9   |   |     | 3 | 
+10  |   |     |   | 3<=3, да
+11  |   | 6   |   |    
+12  |   |     | 4 |    
+13  |   |     |   | 4<=3, нет
+14  |   |вывод|   |    
+
+Трассировка доказывает правильность алгоритма. Теперь запишем этот алгоритм на ЯП. 
+
+```kt
+fun main(){
+    println("Введите n:")
+    val n = readLine()!!.toInt()
+    var F = 1
+    var i = 1
+    while (i<=n){
+        F *= i  // F = F*i
+        i++     // i = i+1
+    }
+    println("F=$F")
+}
+```
+
+Этот алгоритм имеет циклическую структуру. В алгоритме использована структурная команда цикл-пока, или цикл с предусловием. Общий вид команды цикл-пока в блок-схемах и в ЯП следующий: 
+
+![цикл с предусловием](../img/t1l2p8.png)
+
+```kt
+while (условие) {
+  //серия
+}
+```
+
+Выполнение серии команд (тела цикла) повторяется, пока условие цикла истинно. Когда условие становится ложным, цикл заканчивает выполнение.
+
+Цикл с предусловием — это основная, но не единственная форма организации циклических алгоритмов. Другим вариантом является цикл с постусловием. Вернемся к алгоритму решения квадратного уравнения. К нему можно подойти с такой позиции:  
+
+если а = 0, то это уже не квадратное уравнение и его можно не рассматривать. В таком случае будем считать, что пользователь ошибся при вводе данных, и следует предложить ему повторить ввод. Иначе говоря, в алгоритме будет предусмотрен контроль достоверности исходных данных с предоставлением пользователю возможности исправить ошибку. Наличие такого контроля — еще один признак хорошего качества программы. 
+
+![решение квадратного уравнения, блок-схема](../img/t1l2p9.png)
+
+```kt
+fun main(){
+    var a: Int
+    do {
+        println("Введите a:")
+        a = readLine()!!.toInt()
+    } while(a!=0)
+
+    val d = b*b-4*a*c
+    if(d<0) println("нет вещественных корней")
+    else{
+        val x1 = (-b+sqrt(d.toFloat()))/(2*a)
+        val x2 = (-b-sqrt(d.toFloat()))/(2*a)
+        println("x1=$x1, x2=$x2")
+    }
+}
+```
+
+В общем виде структурная команда цикл с постусловием или цикл — до представляется так: 
+
+![цикл с постусловием](../img/t1l2p10.png)
+
+```kt
+do {
+  //серия
+} while (условие)
+```
+
+Здесь используется условие окончания цикла. Когда оно становится истинным, цикл заканчивает работу. 
+
+Составим алгоритм решения следующей задачи: даны два натуральных числа М и N. Требуется вычислить их наибольший общий делитель — НОД(M, N). 
+
+Эта задача решается с помощью метода, известного под названием алгоритма Евклида. Его идея основана на том свойстве, что если M>N, то НОД(М, N) = НОД(М-N,N). Другой факт, лежащий в основе алгоритма, тривиален — НОД(М, М) = М. Для «ручного» выполнения этот алгоритм можно описать в форме следующей инструкции: 
+
+1. Если числа равны, то взять их общее значение в качестве ответа; в противном случае продолжить выполнение алгоритма
+2. Определить большее из чисел
+3. Заменить большее число разностью большего и меньшего значений
+4. Вернуться к выполнению пункта 1
+
+![блок-схема НОД](../img/t1l2p11.png)
+
+```kt
+fun main(){
+    println("Введите m, n: ")
+    var m = readLine()!!.toInt()
+    var n = readLine()!!.toInt()
+    while (m!=n){
+        if(m>n) m = m-n
+        else n = n-m
+    }
+    println("НОД = $m")
+}
+```
+
+Алгоритм имеет структуру цикла с вложенным ветвлением. Проделайте самостоятельно трассировку этого алгоритма для случая М = 18, N = 12. В результате получится НОД = 6, что, очевидно, верно. 
+
+## Вспомогательные алгоритмы и процедуры
+
+В теории алгоритмов известно понятие вспомогательного алгоритма. Вспомогательным называется алгоритм решения некоторой подзадачи из основной решаемой задачи. В таком случае алгоритм решения исходной задачи называется основным алгоритмом.
+
+В качестве примера рассмотрим следующую задачу: требуется составить алгоритм вычисления степенной функции с целым показателем ***у = х<sup>к</sup>***, где к — целое число, х<>0. В алгебре такая функция определена следующим образом: 
+
+![](../img/t1l2p12.png)
+
+Для данной задачи в качестве подзадачи можно рассматривать возведение числа в целую положительную степень.
+
+Учитывая, что ***1/х<sup>-n</sup> = (1/х)<sup>-n</sup>***, запишем основной алгоритм решения этой задачи. 
+
+```kt
+fun main(){
+    println("Введите x, n: ")
+    var x = readLine()!!.toFloat()
+    var n = readLine()!!.toInt()
+    var y: Float
+
+    if(n==0) y = 1F
+    else {
+        if(n>0) y = stepen(x, n)
+        else y = stepen(1/x, -n)
+    }
+   
+    println("y = $y")
+}
+```
+
+Здесь дважды присутствует команда обращения к вспомогательному алгоритму с именем **stepen**. Это алгоритм возведения вещественного основания в целую положительную степень путем его многократного перемножения. Величины, стоящие в скобках в команде обращения к вспомогательному алгоритму, называются фактическими параметрами.
+
+В котлине вспомогательные алгоритмы оформляются в виде функций. Запишем функцию stepen.
+
+```kt
+fun stepen(x: Float, n: Int): Float {
+    var res = 1F
+    var i = 1
+    while(i<=n){
+        res = res * x
+        i++
+    }
+    return res
+}
+```
+
+Заголовок вспомогательного алгоритма начинается с ключевого слова **fun**, после которого следует имя функции, в скобках — список формальных параметров и после скобок тип результата (не обязателен). В списке параметров перечисляются переменные-аргументы с указанием их типов. Здесь x и n — формальные параметры-аргументы. Следовательно, процедура **stepen** производит вычисления по формуле ***а<sup>к</sup>***. В основном алгоритме «Степенная функция» обращение к процедуре производится путем указания ее имени с последующим в скобках списком фактических параметров. Между формальными и фактическими параметрами процедуры должны выполняться следующие правила соответствия: 
+
+* по количеству (сколько формальных, столько и фактических параметров)
+* по последовательности (первому формальному соответствует первый фактический параметр, второму — второй и т.д.)
+* по типам (типы соответствующих формальных и фактических параметров должны совпадать)
+
+Фактические параметры-аргументы могут быть выражениями соответствующего типа.
+
+Обращение к процедуре инициирует следующие действия:
+
+1. Значения параметров-аргументов присваиваются соответствующим формальным параметрам.
+2. Выполняется тело процедуры (команды внутри процедуры).
+3. Значение результата возвращается командой return, и происходит переход к выполнению следующей команды основного алгоритма.
+
+В функции **stepen** нет команд ввода исходных данных и вывода результатов. Здесь присваивание начальных значений аргументам (x, n) производится через передачу параметров-аргументов. А получение результата происходит командой return. Таким образом, передача значений параметров процедур — это третий способ присваивания (наряду с командой присваивания и командой ввода).
+
+Использование процедур позволяет строить сложные алгоритмы методом *последовательной детализации*.
+
+## Программы для графического отображения алгоритмов
+https://draw.io (онлайн)  
+Microsoft Visio  
+Dia (бесплатная) 
+
+---
+
+**КОНТРОЛЬНЫЕ ВОПРОСЫ**
+
+1. [Линейный алгоритм](#линейный-алгоритм)
+1. [Разветвляющийся алгоритм](#разветвляющийся-алгоритм)
+1. [Циклический алгоритм](#циклический-алгоритм)
+1. [Вспомогательные алгоритмы и процедуры](#вспомогательные-алгоритмы-и-процедуры)
+
+---
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Основные понятия алгоритмизации](./t1l1.md) | [Содержание](../readme.md#тема-1-основные-принципы-алгоритмизации-и-программирования) | [Логические основы алгоритмизации](./t1l3.md)
+

+ 352 - 0
articles/t1l3.md

@@ -0,0 +1,352 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Основные алгоритмические конструкции](./t1l2.md) | [Содержание](../readme.md#тема-1-основные-принципы-алгоритмизации-и-программирования) | [Языки программирования](./t2l1.md)
+
+# Логические основы алгоритмизации
+
+*Логика* – это наука о формах и способах мышления (первые учения – Древний Восток).
+
+Начало исследований в области формальной логики было положено Аристотелем в IV в. до н.э. Однако математические подходы к этим вопросам были впервые указаны Джорджем Булем, который положил в основу математической логики алгебру логики (булеву, а логические значения называют булевыми). Алгебра логики используется при построении основных узлов ЭВМ, например, таких как шифратор, сумматор и др.
+
+## Основные формы мышления
+
+Основными формами мышления являются *понятие*, *высказывание* и *умозаключение*.
+
+**Понятие** – это форма мышления, фиксирующая основные, существенные признаки объекта (например, прямоугольник, компьютер).
+
+**Умозаключение** – это форма мышления, с помощью которой из одного или нескольких суждений может быть получено новое суждение – заключение (например, все углы равнобедренного треугольника равны → это треугольник равносторонний).
+
+Составляющей алгоритмов являются логические условия, вычисление значений которых происходит в соответствии с аксиомами алгебры логики.
+
+Основу математической логики составляет алгебра высказываний. 
+
+Объектами, с которыми работает алгебра высказываний, являются повествовательные предложения, относительно которых можно сказать, истинны они или ложны.
+
+### Логическое высказывание
+
+**Высказыванием** называется утверждение, о котором можно определенно сказать, истинно оно или ложно. Высказываний одновременно истинных и ложных не бывает.
+
+Приведем примеры высказываний:
+
+1. Москва - столица России
+2. число 27 является простым
+3. Волга впадает в Каспийское море
+
+Высказывания `1` и `3` являются истинными. Высказывание `2` - ложным, потому что число `27` составное `27 = 3 * 3 * 3`.
+
+Следующие предложения высказываниями не являются:
+
+1. давай пойдем гулять
+2. `2 * x > 8`
+3. `a * x2 + b * x + c = 0`
+4. который час?
+
+Подчеркнем еще раз, что отличительным признаком высказывания является свойство быть истинным или ложным, последние четыре предложения этим свойством не обладают. Невозможно отнести неравенство `2` или уравнение `3` к высказываниям пока не определено значение `x`. При `x = 3` высказывание `2 * 3 > 8` ложно, а при `x = 5` `2 * 5 > 8` - истинно.
+
+Условимся обозначать высказывания большими буквами и, следуя Джорджу Булю, истинное (true) высказывание `A` обозначим так, `A = 1`. В том случае, когда `A` - ложное (false) высказывание, будем писать: `A = 0`.
+
+Функция `ƒ(x1, x2, ..., xn)` называется логической или булевой, если она, так же как и ее аргументы `xi`, может принимать только два значения: "истина" или "ложь".
+
+По числу переменных различают логические функции одной, двух и многих переменных.
+
+### Способы описания логических функций
+
+Логические функции могут быть описаны различными способами:
+
+* в виде таблицы истинности;
+* совершенными нормальными формами;
+* в виде формулы.
+
+Чаще всего встречаются табличная форма представления логической функции (в виде таблицы истинности) и ее аналитическое представление (например, в виде дизъюнктивных или конънктивных форм). 
+
+Из простых высказываний можно строить сложные, называемые составными высказывания, соединяя простые логическими операциями. Над простыми высказываниями определены следующие операции:
+
+1. логическое отрицание
+2. логическое умножение
+3. логическое сложение
+4. ~~логическое следование или импликация~~
+5. ~~эквивалентность~~
+
+Рассмотрим некоторые из этих операций более подробно.
+
+## Логические операции 
+
+### **Инверсия** (логическое отрицание - НЕ) - обозначение ***!***.  
+
+Логическое сложение, умножение, следование и эквивалентность являются бинарными операциями ("би" - два), потому что соединяют два операнда (два высказывания). В отличие от них, логическое отрицание является унарной операцией, потому что применяется лишь к одному высказыванию.
+
+Присоединение частицы НЕ к сказуемому простого высказывания `A` называется операцией логического отрицания.
+
+Результат будет истинным, если исходное высказывание ложно, и наоборот, ложным - если исходное высказывание истинно.
+
+A |!A 
+:-:|:-:
+0 | 1 
+1 | 0 
+
+### **Конъюнкция** (логическое умножение - И) – обозначение ***&*** или ***&&***
+
+Результат будет истинным тогда и только тогда, когда оба исходных высказывания истинны.
+
+A | B | A & B
+:-:|:-:|:---:
+0 | 0 | 0
+1 | 0 | 0
+0 | 1 | 0
+1 | 1 | 1
+
+Для более легкого запоминания этой функции следует придерживаться правила: функция конъюнкции ложна, если ложен хотя бы один из ее операндов. Это правило действует для функции, содержащей произвольное число операндов. Если обозначать значение "истина" как `1`, а значение "ложь" как `0`, то эта функция будет похожа на математическую функцию умножения. Поэтому ее зачастую называют функцией логического умножения.
+
+Отметим, что для конъюнкции, так же как и для следующей рассматриваемой функции – дизъюнкции – действуют ассоциативный (сочетательный) и коммуникативный (переместительный) законы.
+
+В то же время для некоторых других логических функций тот или иной закон не действует. Некоторые из примеров таких функций мы рассмотрим ниже.
+
+Чем отличается оператор **&** от **&&**:
+
+* Оператор **&** всегда вычисляет оба операнда
+* Оператор **&&** вычисляет второй операнд, только если это необходимо.
+
+### **Дизъюнкция** (логическое сложение - ИЛИ) – обозначение **|** или **||**. 
+
+Результат будет истинным тогда, когда истинно хотя бы одно из высказываний.
+
+ A | B | A \| B
+:-:|:-:|:-:
+ 0 | 0 | 0
+ 1 | 0 | 1
+ 0 | 1 | 1
+ 1 | 1 | 1
+
+Для более легкого запоминания этой функции следует придерживаться правила: функция дизъюнкции истинна, если истенен хотя бы один из ее операндов. Это правило действует для функции, содержащей произвольное число операндов. Если обозначать значение "истина" как `1`, а значение "ложь" как `0`, то эта функция для двух операндов будет похожа на математическую функцию поразрядного сложения. Поэтому ее зачастую называют функцией логического сложения. Хотя здесь следует иметь в виду, что в логическом сложении сигнал переноса в более старший разряд не вырабатывается.
+
+Чем отличается оператор **|** от **||**:
+
+* Оператор **|** всегда вычисляет оба операнда
+* Оператор **||** вычисляет второй операнд, только если это необходимо.
+
+### **Сложе́ние по мо́дулю 2** (исключа́ющее «ИЛИ», логи́ческая неравнозна́чность) - обозначение **`^`**. 
+
+Таблица истинности:
+
+ A | B | A `^` B
+:-:|:-:|:-:
+ 0 | 0 | 0
+ 0 | 1 | 1
+ 1 | 0 | 1
+ 1 | 1 | 0
+
+Для операндов целочисленных типов оператор **`^`** вычисляет побитовое исключающее ИЛИ своих операндов.
+
+Про функцию *Сумма по модулю 2* поговорим здесь поподробнее, так она является основой для реализации устройств контроля и защиты информации.
+
+**Пример контроля**
+
+Пусть мы хотим передать некоторые данные, например, код `11001100`. Перед началом передачи код дополняется контрольным разрядом. 
+
+бит данных | действие | сумма по модулю 2
+:-:|:-:|:-:
+ 1 | -   | 1
+ 1 | 1 `^` 1 | 0
+ 0 | 0 `^` 0 | 0
+ 0 | 0 `^` 0 | 0
+ 1 | 1 `^` 0 | 1
+ 1 | 1 `^` 1 | 0
+ 0 | 0 `^` 0 | 0
+ 0 | 0 `^` 0 | 0
+
+В нашем примере контрольный разряд будет равен `0` (сложим `4` единицы и разделим по модулю `2`, результат `0`).
+
+Контрольный разряд передается вместе с основным кодом (`11001100`+`0`).  
+
+Если в процессе передачи произойдет искажение одного разряда (например, последний разряд кода примет значение `1`), то приёмное устройство примет код `11001101` и контрольный разряд `0`, равный исходному (там искажения нет). 
+
+бит данных | действие | сумма по модулю 2
+:-:|:-:|:-:
+ 1 | -   | 1
+ 1 | 1 `^` 1 | 0
+ 0 | 0 `^` 0 | 0
+ 0 | 0 `^` 0 | 0
+ 1 | 1 `^` 0 | 1
+ 1 | 1 `^` 1 | 0
+ 0 | 0 `^` 0 | 0
+ 1 | 1 `^` 0 | 1
+
+Приемное устройство вычисляет контрольный разряд от принятого кода (теперь он будет равен `1`, `5 mod 2 = 1`), сравнивает его с принятым контрольным разрядом (они не совпадают) и сообщает об ошибке в передаче данных. Обычно после этого передача повторяется.
+
+Такой алгоритм только демонстрирует принцип формирования контрольных сумм. В современной технике никто, разумеется, по байтам данные не передает. Контроль обычно осуществляется на уровне пакета данных добавлением циклического кода целостности (CRC16, CRC32).
+
+**Пример защиты**
+
+>A ^ B = C, C ^ B = A !!!  
+
+Почему то нигде не написано про самое интересное свойство этой функции: если к результату повторно применить аналогичное преобразование, то получим исходные данные.
+
+Эту логическую операцию можно применять для элементарной защиты передаваемой информации. Можно использовать так называемый ключ защиты, который должен быть у передающей и принимающей стороны. Пусть в нашем случае таким ключом защиты будет код `01101101`.
+
+данные | ключ | зашифрованные данные
+:-:|:-:|:-:
+ 1 | 0 | 1
+ 1 | 1 | 0
+ 0 | 1 | 1
+ 0 | 0 | 0
+ 1 | 1 | 0
+ 1 | 1 | 0
+ 0 | 0 | 0
+ 0 | 1 | 1
+
+Суммируя по модулю 2 поразрядно передаваемый код и код защиты, получим, что передаваться будет код `10100001`, который отличается от исходного кода. Даже если в случае несанкционированного доступа эта информация будет перехвачена, она ничего не даст злоумышленнику. В то же время, принимающая сторона, имея тот же самый ключ защиты, восстановит исходную информацию.
+
+Если длина передаваемых данных больше длины ключа, то ключ используется повторно для следующего блока данных.
+
+**Свойства функции *сложения по модулю 2***
+
+1. Коммутативность.
+
+    `A ^ B = B ^ A`
+
+2. Ассоциативность.
+
+    `A ^ (B ^ C) = (A ^ B) ^ C`
+
+3. Дистрибутивность относительно конъюнкции.
+
+    `A & (B ^ C) = A & B ^ A & C`
+
+4. Идемпотентность
+
+    `A ^ 0 = A`
+
+5. Отрицание
+
+    `A ^ 1 = !A`
+
+Имеют место следующие очевидные соотношения:  
+
+`A ^ A = 0`
+
+`A ^ !A = 1`
+
+>Википедия знает еще несколько логических операций *Стре́лка Пи́рса* (ИЛИ-НЕ), *Штрих Ше́ффера* (И-НЕ), *Логическое следование* (импликация, "если то"), *Логическая равнозначность или эквивале́нция* (эквивале́нтность).  
+В программировании они не используются, поэтому останавливаться на них мы не будем.
+
+## Приоритет логических операций
+
+* Инверсия ( **!** )
+* Конъюнкция ( **&** )
+* Сложение по модулю 2 ( **^** )
+* Дизъюнкция ( **|**)
+* Условная конъюнкция ( **&&** )
+* Условная дизъюнкция ( **||** )
+
+Порядок вычисления, определяемый приоритетом операторов, можно изменить с помощью скобок (()).
+
+## Законы логических операций
+
+В алгебре логики доказано, что любую логическую функцию можно выразить только через комбинацию логических операций `И`, `ИЛИ` и `НЕ`. Для приведения логических выражений к эквивалентным, но более простым в записи используют ряд логических законов.
+
+1. *Закон противоречия* говорит о том, что никакое предложение не может быть истинно одновременно со своим отрицанием. "это яблоко спелое" и "это яблоко неспелое".
+
+    `А & !А = 0`
+
+2. *Закон исключенного третьего* говорит о том, что для каждого высказывания имеются лишь две возможности: это высказывание либо истинно, либо ложно. Третьего не дано. "Сегодня я либо получу 5, либо не получу". Истинно либо суждение, либо его отрицание.
+
+    `А | !А = 1`
+
+3. *Закон двойного отрицания* заключается в том, что отрицать отрицание какого-нибудь высказывания - то же, что утверждать это высказывание.
+
+    `!(!А) = А`
+
+4. *закон идемпотентности* говорит о том, что в алгебре логики нет показателей степеней и коэффициентов. Конъюнкция одинаковых "сомножителей" равносильна одному из них. Дизъюнкция одинаковых "слагаемых" равносильна одному из них.  
+
+    `А & А = А`
+
+    `А | А = А`
+
+5. *Законы коммутативности и ассоциативности* говорят о том, что конъюнкция и дизъюнкция аналогичны одноименным знакам умножения и сложения чисел.
+
+    коммутативность:
+
+    `А & В = В & А`
+
+    `А | В = В | А`
+
+    ассоциативность:
+
+    `А | (В | С) = (А | В) | С`
+
+    `А & (В & С) = (А & В) & С`
+
+
+6. *Законы дистрибутивности* говорят о том, что логическое сложение и умножение равноправны по отношению к дистрибутивности: не только конъюнкция дистрибутивна относительно дизъюнкции, но и дизъюнкция дистрибутивна относительно конъюнкции.
+
+    `А & (В | С) = (А & В) | (А & С)`
+
+    `А | (В & С) = (А | В) & (А | С)`
+
+7. *Законы поглощения констант* утверждают, что ложь не влияет на значение логического выражения при дизъюнкции, а истина - при конъюнкции.
+
+    `А & 1 = А`
+
+    `А | 0 = А`
+
+8. *Законы поглощения* показывают как упрощать логические выражения при повторе операнда.
+
+    `A | (A & B) = A`
+
+    `A & (A | B) = A`
+
+9. *Законы де Мо́ргана* (правила де Мо́ргана) — логические правила, связывающие пары логических операций при помощи логического отрицания. Названы в честь шотландского математика Огастеса де Моргана. В краткой форме звучат так:
+
+    Отрицание конъюнкции есть не что иное, как дизъюнкция отрицаний.
+
+    `!(А & В) = !А | !В`
+
+    Отрицание дизъюнкции есть не что иное, как конъюнкция отрицаний.
+
+    `!(А | В) = !А & !В`
+
+## Решение логических выражений
+
+Решение логических выражений записывают в виде таблиц истинности – таблиц, в которых по действиям показано, какие значения принимает логическое выражение при всех возможных наборах его переменных.
+
+Для составления таблиц истинности необходимо:
+
+* определить количество строк в таблице: **`2ⁿ`**, n–количество переменных
+* определить количество столбцов в таблице: количество логических переменных + количество логических операций
+* установить последовательность выполнения логических операций
+* построить таблицу, указывая названия столбцов и возможные наборы значений исходных логических переменных
+* заполнить таблицу истинности по столбцам
+
+Например, построим таблицу истинности для ассоциативного закона дизьюнкции: `А | (В | С)`
+
+* количество строк: **`2³ = 8`**
+* количество столбцов: **`3 + 2 = 5`**
+
+ A | B | C | B \| C | A \| (B \| C) 
+:-:|:-:|:-:|:------:|:------------: 
+ 0 | 0 | 0 |  0     | 0
+ 0 | 0 | 1 |  1     | 1
+ 0 | 1 | 0 |  1     | 1
+ 0 | 1 | 1 |  1     | 1
+ 1 | 0 | 0 |  0     | 1
+ 1 | 0 | 1 |  1     | 1
+ 1 | 1 | 0 |  1     | 1
+ 1 | 1 | 1 |  1     | 1
+
+---
+
+**КОНТРОЛЬНЫЕ ВОПРОСЫ**
+
+1. [Основные формы мышления.](#основные-формы-мышления)
+1. [Что такое логическое высказывание?](#логическое-высказывание) 
+1. [Способы описания логических функций.](#способы-описания-логических-функций)
+1. [Основные логические операции.](#логические-операции)
+1. [Приоритет логических операций](#приоритет-логических-операций)
+1. [Законы логических операций](#законы-логических-операций)
+1. [Проверка законов построением таблиц истинности](#решение-логических-выражений)
+
+---
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Основные алгоритмические конструкции](./t1l2.md) | [Содержание](../readme.md#тема-1-основные-принципы-алгоритмизации-и-программирования) | [Языки программирования](./t2l1.md)

+ 3 - 0
articles/t1lab1.md

@@ -0,0 +1,3 @@
+# Лабораторная работа "Разработка алгоритмов для конкретных задач"
+
+http://cs.petrsu.ru/studies/filatova_information/CMD_1996566_M/my_files/Inform/Algorithm/algoritm.pdf

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 170 - 0
articles/t2l1.md


+ 462 - 0
articles/t2l2.md

@@ -0,0 +1,462 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Языки программирования](./t2l1.md) | [Содержание](../readme.md#тема-2-языки-и-методы-программирования) | [Системы контроля версий](./skv.md)
+
+# Методы программирования
+
+Основными технологиями разработки программного обеспечения являются
+
+* Императивное программирование
+* Структурное программирование
+* Модульное программирование
+* Объектно-ориентированное программирование
+
+## Императивное программирование
+
+*Императивное программирование* — это исторически первая методология программирования, которой пользовался каждый программист, программирующий на любом из «массовых» языков программирования – Basic, Pascal, C.
+
+Она ориентирована на классическую фон Неймановскую модель, остававшуюся долгое время единственной аппаратной архитектурой. Методология императивного программирования характеризуется принципом последовательного изменения состояния вычислителя пошаговым образом. При этом управление изменениями полностью определено и полностью контролируемо.
+
+Методы и концепции.
+
+* Метод изменения состояний — заключается в последовательном изменении состояний. Метод поддерживается концепцией алгоритма.
+* Метод управления потоком исполнения — заключается в пошаговом контроле управления. Метод поддерживается концепцией потока исполнения.
+ 
+>Вычислительная модель. Если под вычислителем понимать современный компьютер, то его состоянием будут значения всех ячеек памяти, состояние процессора (в том числе — указатель текущей команды) и всех сопряженных устройств. Единственная структура данных — последовательность ячеек (пар «адрес» - «значение») с линейно упорядоченными адресами.
+
+В качестве математической модели императивное программирование использует машину Тьюринга-Поста — абстрактное вычислительное устройство, предложенное на заре компьютерной эры для описания алгоритмов.
+
+Синтаксис и семантика. Языки, поддерживающие данную вычислительную модель, являются как бы средством описания функции переходов между состояниями вычислителя. Основным их синтаксическим понятием является оператор. Первая группа — простые операторы, у которых никакая их часть не является самостоятельным оператором (например, оператор присваивания, оператор безусловного перехода, вызова процедуры и т. п.). Вторая группа — структурные операторы, объединяющие другие операторы в новый, более крупный оператор (например, составной оператор, операторы выбора, цикла и т. п.).
+
+Традиционное средство структурирования — подпрограмма (процедура или функция). Подпрограммы имеют параметры и локальные определения и могут быть вызваны рекурсивно. Функции возвращают значения как результат своей работы.
+
+Если в данной методологии требуется решить некоторую задачу для того, чтобы использовать ее результаты при решении следующей задачи, то типичный подход будет таким. Сначала исполняется алгоритм, решающий первую задачу. Результаты его работы сохраняются в специальном месте памяти, которое известно следующему алгоритму, и используются им.
+
+Императивные языки программирования. Императивные языки программирования манипулируют данными в пошаговом режиме, используя последовательные инструкции и применяя их к разнообразным данным. 
+>Считается, что первым алгоритмическим языком программирования был язык Plankalkuel (от plan calculus), разработанный в 1945—1946 годах Конрадом Цузе (Konrad Zuse).
+
+Большинствои из наиболее известных и распространенных императивных языков программирования было создано в конце 50-х — середине 70-х годов XX века. Это период 80-х и 90-х годов соответствует увлечениям новыми парадигмами, и императивных языков в это время практически не появлялось.
+
+**Класс задач**. Императивное программирование наиболее пригодно для решения задач,в которых последовательное исполнение каких-либо команд является естественным. Примером здесь может служить управление современными аппаратными средствами. Поскольку практически все современные компьютеры императивны, эта методология позволяет порождать достаточно эффективный исполняемый код. С ростом сложности задачи императивные программы становятся все менее и менее читаемыми.
+
+Программирование и отладка действительно больших программ (например, компиляторов),написанных исключительно на основе методологии императивного программирования, может затянуться на долгие годы.
+
+## Структурное программирование
+
+Структурное программирование (СП) возникло как вариант решения проблемы уменьшения СЛОЖНОСТИ разработки программного обеспечения.
+
+В начале эры программирования работа программиста ничем не регламентировалась. Решаемые задачи не отличались размахом и масштабностью, использовались в основном машинно-ориентированные языки и близкие к ним язык типа Ассемблера, разрабатываемые программы редко достигали значительных размеров, не ставились жесткие ограничения на время их разработки.
+
+По мере развития программирования появились задачи, для решения которых определялись ограниченные сроки все более сложных задач с привлечением групп программистов. И как следствие, разработчики столкнулись с тем, что методы, пригодные для разработки небольших задач, не могут быть использованы при разработке больших проектов в силу сложности последних.
+
+Таким образом, цель структурного программирования - повышение надежности программ, обеспечение *сопровождения и модификации*, облегчение и ускорение разработки.
+
+*Методология структурного императивного программирования* — подход, заключающийся в задании хорошей топологии императивных программ, в том числе отказе от использования глобальных данных и оператора безусловного перехода, разработке модулей с сильной связностью и обеспечении их независимости от других модулей.
+
+Подход базируется на двух основных принципах:
+
+* Последовательная декомпозиция алгоритма решения задачи сверху вниз.
+* Использование структурного кодирования.
+
+Напомним, что данная методология является важнейшим развитием императивной методологии.
+
+**Происхождение, история и эволюция**. Создателем структурного подхода считается Эдсгер Дейкстра. Ему также принадлежит попытка (к сожалению, совершенно неприменимая для массового программирования) соединить структурное программирование с методами доказательства правильности создаваемых программ. В его разработке участвовали такие известные ученые как Х. Милс, Д.Э. Кнут, С. Хоор.
+
+**Методы и концепции, лежащие в основе структурного программирования**. Их три:
+
+* Метод *алгоритмической декомпозиции сверху вниз* — заключается в пошаговой детализации постановки задачи, начиная с наиболее общей задачи. Данный метод обеспечивает хорошую структурированность. Метод поддерживается концепцией алгоритма.
+* Метод *модульной организации частей программы* — заключается в разбиении программы на специальные компоненты, называемые модулями. Метод поддерживается концепцией модуля.
+* *Метод структурного кодирования* — заключается в использовании при кодировании трех основных управляющих конструкций (последовательное исполнение, ветвление, циклы). Метки и оператор безусловного перехода являются трудно отслеживаемыми связями, без которых мы хотим обойтись.
+
+**Структурные языки программирования**. Основное отличие от классической методологии императивного программирования заключается в отказе (точнее, той или иной степени отказа) от оператора безусловного перехода.
+
+[Пратт Т., 1979] "Важным для программиста свойством синтаксиса является возможность отразить в структуре программы структуру лежащего в ее основе алгоритма. При использовании для построения программы метода, известного под названием структурное программирование, программа конструируется иерархически - сверху вниз (от главной программы к подпрограммам самого нижнего уровня), с употреблением на каждом уровне только ограниченного набора управляющих структур: простых последовательностей инструкций, циклов и некоторых видов условных разветвлений. При последовательном проведении этого метода структуру результирующих алгоритмов легко понимать, отлаживать и модифицировать. В идеале у нас должна появиться возможность перевести построенную таким образом схему программы прямо в соответствующие программные инструкции, отражающие структуру алгоритма."
+
+Теорема о структурировании (Бёма-Джакопини (Boem-Jacopini)): Всякую правильную программу (т.е. программу с одним входом и одним выходом без зацикливаний и недостижимых веток) можно записать с использованием следующих логических структур - последовательность, выбора и повторение цикла
+
+*Следствие 1*: Всякую программу можно привести к форме без оператора goto.
+
+*Следствие 2*: Любой алгоритм можно реализовать в языке, основанном на трех управляющих конструкциях -последовательность, цикл, повторение.
+
+*Следствие 3*: Сложность структурированных программ ограничена, даже в случае их неограниченного размера.
+
+Структурное программирование- это не самоцель. Его основное назначение- это получение хорошей ("правильной") программы, однако даже в самой хорошей программе операторы перехода goto иногда нужны: например - выход из множества вложенных циклов.
+
+Практически на всех языках, поддерживающих императивную методологию, можно разрабатывать программы и по данной методологии. В ряде языков введены специальные заменители оператора goto, позволяющие облегчить управление циклами (например, Break и Continue в языке C).
+
+**Класс задач**. Класс задач для данной методологии соответствует классу задач для императивной методологии. Заметим, что при этом удается разрабатывать более сложные программы, поскольку их легко воспринимать и анализировать.
+
+## Модульное программирование
+
+Модульное программирование - это такой способ программирования, при котором вся программа разбивается на группу компонентов, называемых модулями, причем каждый из них имеет свой контролируемый размер, четкое назначение и детально проработанный интерфейс с внешней средой. Единственная альтернатива модульности — монолитная программа, что, конечно, неудобно. Таким образом, наиболее интересный вопрос при изучении модульности — определение критерия разбиения на модули.
+
+### Концепции модульного программирования. 
+
+В основе модульного программирования лежат три основных концепции:
+
+1. Принцип утаивания информации Парнаса. Всякий компонент утаивает единственное проектное решение, т.е. модуль служит для утаивания информации. Подход к разработке программ заключается в том, что сначала формируется список проектных решений, которые особенно трудно принять или которые, скорее всего, будут меняться. Затем определяются отдельные модули, каждый из которых реализует одно из указанных решений.
+
+2. Аксиома модульности Коуэна. Модуль — независимая программная единица, служащая для выполнения некоторой определенной функции программы и для связи с остальной частью программы. Программная единица должна удовлетворять следующим условиям:
+
+    - блочность организации, т.е. возможность вызвать программную единицу из блоков любой степени вложенности;
+    - синтаксическая обособленность, т.е. выделение модуля в тексте синтаксическими элементами;
+    - семантическая независимость, т.е. независимость от места, где программная единица вызвана;
+    - общность данных, т.е. наличие собственных данных, сохраняющихся при каждом обращении;
+    - полнота определения, т.е. самостоятельность программной единицы.
+
+3. Сборочное программирование Цейтина. Модули — это программные кирпичи, из которых строится программа. Существуют три основные предпосылки к модульному программированию:
+
+    - стремление к выделению независимой единицы программного знания. В идеальном случае всякая идея (алгоритм) должна быть оформлена в виде модуля;
+    - потребность организационного расчленения крупных разработок;
+    - возможность параллельного исполнения модулей (в контексте параллельного программирования).
+
+**Определения модуля и его примеры**. Приведем несколько дополнительных определений модуля.
+
+* Модуль — это совокупность команд, к которым можно обратиться по имени.
+* Модуль — это совокупность операторов программы, имеющая граничные элементы и идентификатор (возможно агрегатный).
+
+Функциональная спецификация модуля должна включать:
+
+* синтаксическую спецификацию его входов, которая должна позволять построить на используемом языке программирования синтаксически правильное обращение к нему;
+* описание семантики функций, выполняемых модулем по каждому из его входов.
+
+**Разновидности модулей**. Существуют три основные разновидности модулей:
+
+1. "Маленькие" (функциональные) модули, реализующие, как правило, одну какую-либо определенную функцию. Основным и простейшим модулем практически во всех языках программирования является процедура или функция.
+
+1. "Средние" (информационные) модули, реализующие, как правило, несколько операций или функций над одной и той же структурой данных (информационным объектом), которая считается неизвестной вне этого модуля. Примеры "средних" модулей в языках программирования:
+
+    * задачи в языке программирования Ada;
+    * кластер в языке программирования CLU;
+    * классы в языках программирования C++ и Java.
+
+1. "Большие” (логические) модули, объединяющие набор "средних" или "маленьких" модулей. Примеры "больших" модулей в языках программирования:
+
+   * модуль в языке программирования Modula-2;
+   * пакеты в языках программирования Ada и Java.
+
+Набор **характеристик** модуля предложен Майерсом [Майерс 1980]. Он состоит из следующих конструктивных характеристик:
+
+1. размера модуля;
+
+    В модуле должно быть 7 (+/-2) конструкций (например, операторов для функций или функций для пакета). Это число берется на основе представлений психологов о среднем оперативном буфере памяти человека. Символьные образы в человеческом мозгу объединяются в "чанки" — наборы фактов и связей между ними, запоминаемые и извлекаемые как единое целое. В каждый момент времени человек может обрабатывать не более 7 чанков.  
+
+    Модуль (функция) не должен превышать 60 строк. В результате его можно поместить на одну страницу распечатки или легко просмотреть на экране монитора.
+
+1. прочности (связности) модуля;  
+
+    Существует гипотеза о глобальных данных, утверждающая, что глобальные данные вредны и опасны. Идея глобальных данных дискредитирует себя так же, как и идея оператора безусловного перехода goto. Локальность данных дает возможность легко читать и понимать модули, а также легко удалять их из программы.  
+
+    *Связность* (прочность) модуля — мера независимости его частей. Чем выше связность модуля — тем лучше, тем больше связей по отношению к оставшейся части программы он упрятывает в себе. Можно выделить типы связности, приведенные ниже.  
+
+    *Функциональная связность*. Модуль с функциональной связностью реализует одну какую-либо определенную функцию и не может быть разбит на 2 модуля с теми же типами связностей.  
+
+    *Последовательная связность*. Модуль с такой связностью может быть разбит на последовательные части, выполняющие независимые функции, но совместно реализующие единственную функцию. Например, один и тот же модуль может быть использован сначала для оценки, а затем для обработки данных.  
+
+    *Информационная* (коммуникативная) связность. Модуль с информационной связностью — это модуль, который выполняет несколько операций или функций над одной и той же структурой данных (информационным объектом), которая считается неизвестной вне этого модуля. Эта информационная связность применяется для реализации абстрактных типов данных.  
+
+    Обратим внимание на то, что средства для задания информационно прочных модулей отсутствовали в ранних языках программирования (например, FORTRAN и даже в оригинальной версии языка Pascal). И только позже, в языке программирования Ada, появился пакет — средство задания информационно прочного модуля.
+
+1. сцепления модуля с другими модулями;  
+
+    *Сцепление* — мера относительной независимости модуля от других модулей. Независимые модули могут быть модифицированы без переделки других модулей. Чем слабее сцепление модуля, тем лучше. Рассмотрим различные типы сцепления.
+
+    * *Независимые модули* — это идеальный случай. Модули ничего не знают друг о друге. Организовать взаимодействие таких модулей можно, зная их интерфейс и соответствующим образом перенаправив выходные данные одного модуля на вход другого. Достичь такого сцепления сложно, да и не нужно, поскольку сцепление по данным (параметрическое сцепление) является достаточно хорошим.
+
+    * Сцепление по данным (параметрическое) — это сцепление, когда данные передаются модулю, как значения его параметров, либо как результат его обращения к другому модулю для вычисления некоторой функции. Этот вид сцепления реализуется в языках программирования при обращении к функциям (процедурам). Две разновидности этого сцепления определяются характером данным.
+       - Сцепление по простым элементам данных.
+       - Сцепление по структуре данных. В этом случае оба модуля должны знать о внутренней структуре данных.
+
+1. рутинности (идемпотентность, независимость от предыдущих обращений) модуля.
+
+    *Рутинность* — это независимость модуля от предыдущих обращений к нему (от предыстории). Будем называть модуль рутинным, если результат его работы зависит только от количества переданных параметров (а не от количества обращений).
+
+    Модуль должен быть рутинным в большинстве случаев, но есть и случаи, когда модуль должен сохранять историю. В выборе степени рутинности модуля пользуются тремя рекомендациями.
+
+    * В большинстве случаев делаем модуль рутинным, т. е. независимым от предыдущих обращений.
+    * Зависящие от предыстории модули следует использовать только в тех случаях, когда это необходимо для сцепления по данным.
+    * В спецификации зависящего от предыстории модуля должна быть четко сформулирована эта зависимость, чтобы пользователи имели возможность прогнозировать поведение такого модуля.
+
+## Метод объектно-ориентированного программирования (ООП).
+
+Метод структурного программирования оказался эффективен при написании программ «ограниченной сложности». Однако с возрастанием сложности реализуемых программных проектов и, соответственно, объема кода создаваемых программ, возможности метода структурного программирования оказались недостаточными.
+
+Основной причиной возникших проблем можно считать то, что в программе не отражалась непосредственно структура явлений и понятий реального мира и связей межу ними. При попытке анализа и модификации текста программы программист вынужден был оперировать искусственными категориями.
+
+Чтобы писать все более сложные программы, необходим был новый подход к программированию. В итоге были разработаны принципы Объектно-Ориентированного Программирования. OOП аккумулирует лучшие идеи, воплощённые в структурном программировании, и сочетает их с мощными новыми концепциями, которые позволяют по-новому организовывать ваши программы.
+
+Надо сказать, что теоретические основы ООП были заложены еще в 70-х годах прошлого века, но практическое их воплощение стало возможно лишь в середине 80-х, с появлением соответствующих технических средств.
+
+Методология ООП использует *метод объектной декомпозиции*, согласно которому структура системы (статическая составляющая) описывается в терминах объектов и связей между ними, а поведение системы (динамическая составляющая) - в терминах обмена сообщениями между объектами. Сообщения могут быть как реакцией на события, вызываемые как внешними факторами, так и порождаемые самими объектами.
+
+Объектно-ориентированные программы называют «программами, управляемыми от событий»,в отличие от традиционных программ, называемых «программам, управляемыми от данных».
+
+Основные **методы и концепции ООП**
+
+* Метод объектно-ориентированной декомпозиции – заключается в выделении объектов и связей между ними. Метод поддерживается концепциями инкапсуляции, наследования и полиморфизма.
+* Метод абстрактных типов данных – метод, лежащий в основе инкапсуляции. Поддерживается концепцией абстрактных типов данных.
+* Метод пересылки сообщений – заключается в описании поведения системы в терминах обмена сообщениями между объектами. Поддерживается концепцией сообщения.
+
+*Вычислительная модель* чистого ООП поддерживает только одну операцию – посылку сообщения объекту. Сообщения могут иметь параметры, являющиеся объектами. Само сообщение тоже является объектом.
+
+Объект имеет набор обработчиков сообщений (набор методов). У объекта есть поля – персональные переменные данного объекта, значениями которых являются ссылки на другие объекты. В одном из полей объекта хранится ссылка на объект-предок, которому переадресуются все сообщения, не обработанные данным объектом. Структуры, описывающие обработку и переадресацию сообщений, обычно выделяются в отдельный объект, называемый классом данного объекта. Сам объект называется экземпляром указанного класса.
+
+**Синтаксис и семантика**
+
+В синтаксисе чистых объектно-ориентированных языков все может быть записано в форме посылки сообщений объектам. Класс в объектно-ориентированных языках описывает структуру и функционирование множества объектов с подобными характеристиками, атрибутами и поведением. Объект принадлежит к некоторому классу и обладает своим собственным внутренним состоянием. Методы — функциональные свойства, которые можно активизировать.
+
+В объектно-ориентированном программировании определяют три основных свойства:
+
+* Инкапсуляция. Это сокрытие информации и комбинирование данных и функций (методов) внутри объекта.
+* Наследование. Построение иерархии порожденных объектов с возможностью для каждого такого объекта-наследника доступа к коду и данным всех порождающих объектов-предков. Построение иерархий является достаточно сложным делом, так как при этом приходится выполнять классифицирование.
+
+    Большинство окружающих нас объектов относится к категориям, рассмотренным в книге [Шлеер, Меллор 1993]:
+
+    * Реальные объекты – абстракции предметов, существующих в физическом мире;
+    * Роли – абстракции цели или назначения человека, части оборудования или организации;
+    * Инциденты – абстракции чего-то произошедшего или случившегося;
+    * Взаимодействия – объекты, получающиеся из отношения между другими объектами.
+
+* Полиморфизм (полиморфизм включения) — присваивание действию одного имени, которое затем разделяется вверх и вниз по иерархии объектов, причем каждый объект иерархии выполняет это действие способом, подходящим именно ему.
+
+У каждого объекта есть ссылка на класс, к которому он относится. При приеме сообщения объект обращается к классу для обработки данного сообщения. Сообщение может быть передано вверх по иерархии наследования, если сам класс не располагает методом для его обработки. Если обработчик событий для сообщения выбирается динамически, то методы, реализующие обработчиков событий, принято называть виртуальными.
+
+Естественным средством структурирования в данной методологии являются классы. Классы определяют, какие поля и методы экземпляра доступны извне, как обрабатывать отдельные сообщения и т. п. В чистых объектно-ориентированных языках извне доступны только методы, а доступ к данным объекта возможен только через его методы.
+
+Взаимодействие задач в данной методологии осуществляется при помощи обмена сообщениями между объектами, реализующими данные задачи.
+
+Для поддержки концепции ООП были разработаны специальные объектно-ориентированные языки программирования. Все языки OOП можно разделить на три группы.
+
+* Чистые языки, в наиболее классическом виде поддерживающие объектно-ориентированную методологию. Такие языки содержат небольшую языковую часть и существенную библиотеку, а также набор средств поддержки времени выполнения.
+* Гибридные языки, которые появились в результате внедрения объектно-ориентированных конструкций в популярные императивные языки программирования.
+* Урезанные языки, которые появились в результате удаления из гибридных языков наиболее опасных и ненужных с позиций ООП конструкций.
+
+## Общие принципы разработки программного обеспечения
+
+Программное обеспечение различается по назначению, выполняемым функциям, формам реализации. В этом смысле всякое ПО — сложная, достаточно уникальная программная система. Однако существуют некоторые общие принципы, которые следует использовать при разработке ПО.
+ 
+* **Частотный** принцип. Основан на выделении в алгоритмах и в обрабатываемых структурах групп действий и данных по частоте использования. Для действий, которые чаще встречаются при работе ПО, обеспечиваются условия их наиболее быстрого выполнения. К данным, к которым происходит частое обращение, обеспечивается наиболее быстрый доступ. «Частые» операции стараются делать более короткими. 
+
+* Принцип **модульности**. Под модулем в общем случае понимают функциональный элемент рассматриваемой системы, имеющий оформление, законченное и выполненное в пределах требований системы, и средства сопряжения с подобными элементами или элементами более высокого уровня данной или другой системы. Способы обособления составных частей ПО в отдельные модули могут быть существенно различными. Чаще всего разделение происходит по функциональному признаку. В значительной степени разделение системы на модули определяется используемым методом проектирования ПО. 
+
+* Принцип **функциональной избирательности**. Этот принцип является логическим продолжением *частотного* и *модульного* принципов и используется при проектировании ПО, объем которого существенно превосходит имеющийся объем оперативной памяти. В ПО выделяется некоторая часть важных модулей, которые постоянно должны быть в состоянии готовности для эффективной организации вычислительного процесса. Эту часть в ПО называют ядром или монитором. В состав монитора, помимо чисто управляющих модулей, должны войти наиболее часто используемые модули. Программы, входящие в состав монитора, постоянно хранятся в оперативной памяти. Остальные части ПО размещаются на внешних запоминающих устройствах и загружаются в оперативную память только по вызову, перекрывая друг друга при необходимости. 
+
+* Принцип **генерируемости**. Основное положение этого принципа определяет такой способ исходного представления ПО, который бы позволял осуществлять настройку на конкретную конфигурацию технических средств, круг решаемых проблем, условия работы пользователя.
+ 
+* Принцип **функциональной избыточности**. Этот принцип учитывает возможность выполнения одной и той же работы (функции) различными средствами. Особенно важен учет этого принципа при разработке пользовательского интерфейса для выдачи данных из-за психологических различий в восприятии информации. 
+
+* Принцип **умолчания**. Применяется для облегчения организации связей с системой как на стадии генерации, так и при работе с уже готовым ПО. Принцип основан на хранении в системе некоторых базовых описаний структур, модулей, конфигураций оборудования и данных, заранее определяющих условия работы с ПО. Эту информацию ПО использует в качестве заданной, если пользователь забудет или сознательно не конкретизирует ее. 
+
+Общесистемные принципы. При создании и развитии ПО рекомендуется применять следующие общесистемные принципы:
+
+* принцип включения, который предусматривает, что требования к созданию, функционированию и развитию ПО определяются со стороны более сложной, включающей его в свой состав системы; 
+* принцип системного единства, который состоит в том, что на всех стадиях создания,функционирования и развития ПО его целостность будет обеспечиваться связями между подсистемами, а также функционированием подсистемы управления; 
+* принцип развития, который предусматривает в ПО возможность его наращивания и совершенствования компонентов и связей между ними; 
+* принцип комплексности, который заключается в том, что ПО обеспечивает связность обработки информации как отдельных элементов, так и для всего объема данных в целом на всех стадиях обработки; 
+* принцип информационного единства, т. е. во всех подсистемах, средствах обеспечения и компонентах ПО используются единые термины, символы, условные обозначения и способы представления; 
+* принцип совместимости, который состоит в том, что язык, символы, коды и средства обеспечения ПО согласованы, обеспечивают совместное функционирование всех его подсистем и сохраняют открытой структуру системы в целом; 
+* принцип инвариантности, который предопределяет, что подсистемы и компоненты ПО инвариантны к обрабатываемой информации, т.е. являются универсальными или типовыми.
+
+## Жизненный цикл программного обеспечения
+
+Понятие "жизненный цикл" предполагает нечто рождаю­щееся, развивающееся и умирающее. Подобно живому организму программные изделия создаются, эксплуатируются и развиваются во времени.
+
+**Жизненный цикл** программного обеспечения включает в себя все этапы его развития: от возникновения потребности в нем до полного прекращения его использования вследствие морального старения или потери необходимости решения соответствующих задач.
+
+Можно выделить несколько фаз существования программного изделия в течение его жизненного цикла. Общепринятых названий для этих фаз и их числа пока еще нет. Но и особых разногласий по этому вопросу нет. Поэтому существует несколько вариантов разбиения жизненного цикла программного обеспечения на этапы. Вопрос о том, лучше ли данное конкретное разбиение, чем другие, не является основным. Главное, необходимо пра­вильно организовать разработку программного обеспечения с их учетом.
+
+По длительности жизненного цикла программные изделия можно разделить на два класса: с **малым** и **большим** временем жизни. Этим классам программ соответствуют гибкий (мягкий) подход к их созданию и использованию и жесткий промыш­ленный подход регламентированного проектирования и эксплуа­тации программных изделий. В научных организациях и вузах, например, преобладают разработки программ первого класса, а в проектных и промышленных организациях — второго.
+
+**Программные изделия с малой длительностью эксплуатации** создаются в основном для решения научных и инженерных задач, для получения конкретных результатов вычислений. Такие прог­раммы обычно относительно невелики. Они разрабатываются одним специалистом или маленькой группой. Главная идея программы обсуждается одним программистом и конечным пользователем. Некоторые детали заносятся на бумагу, и проект реализуется в течение нескольких дней или недель. Они не предназначены для тиражирования и передачи для последующего использования в другие коллективы. По существу, такие прог­раммы являются частью научно-исследовательской работы и не могут рассматриваться как отчуждаемые программные изделия.
+
+Их жизненный цикл состоит из длительного интервала сис­темного анализа и формализации проблемы, значительного этапа проектирования программ и относительно небольшого времени эксплуатации и получения результатов. Требования, предъяв­ляемые к функциональным и конструктивным характеристикам, как правило, не формализуются, отсутствуют оформленные испытания программ. Показатели их качества контролируются только разработчиками в соответствии с их неформальными представлениями.
+
+Сопровождение и модификация таких программ не обязатель­ны, и их жизненный цикл  завершается  после  получения  резуль­татов  вычислений.  Основные  затраты  в  жизненном  цикле  таких программ  приходятся  на  этапы  системного  анализа  и  проектирования,  которые  продолжаются  от  месяца  до 2 лет, в результате чего жизненный цикл программного изделия редко превышает 3 года.
+
+**Программные изделия с большой длительностью эксплуатации** создаются для регулярной обработки информации и управления. Структура таких программ сложная. Их размеры могут изменяться в широких пределах, однако все они обладают свойствами познаваемости и возможности модифи­кации в процессе длительного сопровождения и использования различными специалистами. Программные изделия этого класса допускают тиражирование, они сопровождаются документацией как промышленные изделия и представляют собой отчуждаемые от разработчика программные продукты.
+
+Их проектированием и эксплуатацией занимаются большие коллективы специалистов, для чего необходима формализация программной системы, а также формализованные испытания и определение достигнутых показателей качества конечного про­дукта. Их жизненный цикл составляет десятки лет. До 90% этого времени приходится на эксплуатацию и сопровождение. Вследствие массового тиражирования и длительного сопровождения совокупные затраты в процессе эксплуатации и сопровождения   таких программных изделий значительно превышают затраты на системный анализ и проектирование.
+
+Все последующее изложение акцентирует внимание на теме разработки крупных (сложных)программных средств управления и обработки информации.
+
+Обобщенная модель **жизненного цикла** программного изделия может выглядеть так:
+
+1. Системный анализ:
+    * исследования;
+    * анализ осуществимости:
+        -  эксплуатационной;
+        -  экономической;
+        -  коммерческой.
+
+2. Проектирование программного обеспечения:
+    * конструирование:
+        - функциональная декомпозиция системы, ее архитектура;
+        - внешнее проектирование программного обеспечения;
+        - проектирование базы данных;
+        - архитектура программного обеспечения;
+    * программирование:
+        - внутреннее проектирование программного обеспечения;
+        - внешнее проектирование программных модулей;
+        - внутреннее проектирование программных модулей;
+        - кодирование;
+        - отладка программ;
+        - компоновка программ;
+    * отладка программного обеспечения.
+
+3. Оценка (испытания) программного обеспечения.
+4. Использование программного обеспечения:
+    * эксплуатация;
+    * сопровождение.
+
+**Системный анализ**. В начале разработки программного обеспечения проводят системный анализ (предварительное его проектирование), в ходе которого определяются потребность в нем, его назначение и основные функциональные характе­ристики. Оцениваются затраты и возможная эффективность применения будущего программного изделия.
+
+На этом этапе составляется перечень требований, то есть четкое определение того, что пользователь ожидает от готового продукта. Здесь же осуществляется постановка целей и задач, ради реализации которых и разрабатывается сам проект. В фазе систем­ного анализа можно выделить два направления: исследование и анализ осуществимости.
+
+*Исследования начинаются* с того момента, когда руководитель разработки осознает потребность в программном обеспечении.
+
+Работа состоит в планировании и координации действий, необходимых для подготовки формального рукописного перечня требований к разрабатываемому программному изделию.
+
+*Исследования заканчиваются* тогда, когда требования сформи­рованы в таком виде, что становятся обозримыми и при необ­ходимости могут быть модифицированы и одобрены ответствен­ным руководителем.
+
+*Анализ осуществимости* есть техническая   часть  исследований  и начинается  тогда,  когда  намерение  руководства  окрепнет  настолько, что назначается руководитель проекта, организующий проектирование и распределение ресурсов  (рабочей  силы).
+
+Работа заключается в исследовании предполагаемого прог­раммного изделия с целью получения практической оценки возможности реализации проекта, в частности определяются:
+
+* осуществимость эксплуатационная, будет ли изделие доста­точно удобно для практического использования?
+* осуществимость экономическая, приемлема ли стоимость разрабатываемого изделия? Какова эта стоимость? Будет ли изделие экономически эффективным инструментом в руках пользователя?
+* осуществимость коммерческая,будет ли изделие привлека­тельным, пользоваться спросом, легко устанавливаемым, прис­пособленным к обслуживанию, простым в освоении?    
+
+Эти и другие вопросы необходимо решать главным образом при рассмотрении указанных выше требований.
+
+Анализ осуществимости заканчивается, когда все требования собраны и одобрены.
+
+Прежде чем продолжить дальнейшую работу над проектом необходимо удостовериться, что вся необходимая информация получена. Эта информация должна быть точной, понятной и осуществимой. Она должна представлять собой полный комплекс требований удовлетворяющих пользователя к разрабатываемому программному продукту, оформляемый в виде спецификации.
+
+При несоблюдении данного требования можно значительно замедлить реализацию проекта в будущем вследствие много­кратного повторного обращения к пользователю за уточнением неверно трактованных деталей, неоговоренных условий и, как следствие, потребуется переделка уже разработанных его частей.
+
+Часто в период системного анализа принимается решение о прекращении дальнейшей разработки программного обеспе­чения.
+
+**Проектирование программного обеспечения**. Проектиро­вание является основной и решающей фазой жизненного цикла программного обеспечения, во время которого создается и на 90% приобретает свою окончательную форму программное из­делие.
+
+Эта фаза жизни охватывает различные виды деятельности проекта и может быть разделена на три основных этапа: *конст­руирование, программирование и отладку* программного из­делия.
+
+*Конструирование* программного обеспечения обычно начи­нается ещё в фазе анализа осуществимости, как только оказы­ваются зафиксированными на бумаге некоторые предварительные цели и требования к нему.
+
+К моменту утверждения требований работа в фазе конст­руирования будет в самом разгаре.
+
+На этом отрезке жизни программного обеспечения осу­ществляют:
+
+- функциональную декомпозицию решаемой задачи, на основе которой определяется архитектура системы этой задачи;
+- внешнее проектирование программного обеспечения, вы­ражающееся в форме внешнего взаимодействия его с поль­зователем;
+- проектирование базы данных, если это необходимо;
+- проектирование архитектуры программного обеспечения — определение объектов, модулей и их сопряжения.
+
+*Программирование* начинается уже в фазе конструирования, как только станут доступными основные спецификации на отдельные компоненты программного изделия, но не ранее утверждения соглашения о требованиях. Перекрытие фаз прог­раммирования и конструирования приводит к экономии общего времени разработки, а также к обеспечению проверки пра­вильности проектных решений, и в некоторых случаях влияет на решение ключевых вопросов.
+
+На этом этапе выполняется работа, связанная со сборкой программного изделия. Она состоит в подробном внутреннем конструировании программного продукта, в разработке внут­ренней логики каждого модуля системы, которая затем выра­жается текстом конкретной программы.
+
+Фаза программирования завершается, когда разработчики закончат документирование, отладку и компоновку отдельных частей программного изделия в одно целое.
+
+*Отладка* программного обеспечения осуществляется после того, когда все его компоненты будут отлажены по отдельности и собраны в единый программный продукт.
+
+**Оценка (испытания) программного обеспечения**. В этой фазе программное изделие подвергается строгому системному испы­танию со стороны группы лиц, не являющихся разработчиками.
+
+Это делается для того, чтобы гарантировать, что готовое программное   изделие удовлетворяет всем требованиям и спецификациям, может быть   использовано в среде пользователя, свободно от каких-либо дефектов и содержит необходимую документацию, которая точно и полно описывает программное изделие.
+
+Фаза оценки начинается, как только все компоненты (мо­дули) собраны вместе и испытаны, т.е. после полной отладки готового программного продукта. Она заканчивается после полу­чения подтверждения, что программное изделие прошло все испытания и готово к эксплуатации.
+
+Она продолжается так же долго, как и программирование.
+
+**Использование программного обеспечения**. Если системный анализ - сигнал к бою, проектирование - атака и возвращение с победой, то использование программного изделия -это ежедневная оборона, жизненно необходимая, но обычно не почетная для разработчиков.
+
+Такое сравнение уместно ввиду того, что во время исполь­зования программного изделия исправляются ошибки, вкрав­шиеся в процессе его проектирования.
+
+Фаза использования программного изделия начинается тогда, когда изделие передается в систему распределения.
+
+Это то время, в течение которого изделие находится в действии и используется эффективно.
+
+В это время выполняются обучение персонала, внедрение, настройка, сопровождение и, возможно, расширение прог­раммного изделия - так называемое продолжающееся проек­тирование.
+
+Фаза использования заканчивается, когда изделие изымается из употребления и упомянутые выше действия прекращаются. Отметим, однако, что программное изделие может долго приме­няться кем-либо еще и после того, как фаза использования в том виде, как она определена здесь, завершится. Потому что этот некто может плодотворно использовать программное изделие у себя даже без помощи разработчика.
+
+Использование программного продукта определяется его эксплуатацией и сопровождением.
+
+**Эксплуатация программного изделия** заключается в испол­нении, функционировании его на ЭВМ для обработки инфор­мации и в получении результатов, являющихся целью его соз­дания, а также, в обеспечении достоверности и надежности выдаваемых данных.
+
+**Сопровождение программного обеспечения** состоит  в  эксплу­атаци­он­ном  обслуживании,  развитии  функциональных  возможностей и повышении эксплуатационных характеристик прог­рам­мно­го изделия, в тиражировании и переносе программного изделия на различные типы вычислительных средств.
+
+Сопровождение играет роль необходимой обратной связи от этапа эксплуатации.
+
+В процессе функционирования программного обеспечения возможно обнаружение ошибок в программах, и появляется необходимость их модификации и расширения функций.
+
+Эти доработки, как правило, ведутся одновременно с эксплу­атацией текущей версии программного изделия. После проверки подготовленных корректировок на одном из экземпляров прог­рамм очередная версия программного изделия заменяет ранее эксплуатировавшиеся или некоторые из них. При этом процесс эксплуатации программного изделия может быть практически непрерывным, так как замена версии программного изделия является кратковременной. Эти обстоятельства приводят к тому, что процесс эксплуатации версии программного изделия обычно идет параллельно и независимо от этапа сопровождения.
+
+Возможны и обычно желательны перекрытия между разными фазами жизненного цикла программного изделия. Однако не должно быть никакого перекрытия между несмежными про­цессами.                                         
+
+Возможна обратная связь между фазами. Например, во время одного из шагов внешнего проектирования могут быть обна­ружены погрешности в формулировке целей, тогда нужно не­медленно вернуться и  исправить их.
+
+## Типы приложений
+
+Традиционно приложения делят на две большие группы (по способу взаимодействия с пользователем):
+
+- консольные, ввод и вывод информации в которых производится при помощи стандартных потоков ввода (stdin), поток вывода (stdout) и поток ошибок (stderr).
+
+    Стандартные потоки открываются автоматически при запуске программы и связаны по умолчанию с монитором. Хотя вывод может быть перенаправлен в файл (или из файла) средствами операционной системы (>>, <<, >, <).   
+
+    Взаимодействие с программой сводится к передаче параметров командную строку или интерактивно через поток ввода и выдачи программой текстовой и символьной информации через поток вывода или ошибок.   
+
+    Одним из недостатков консольных приложений считается необходимость ввода команд, достоинством - лёгкое встраивание в скрипты и автоматизация действий.  
+    В графических операционных системах (Windows, Mac), консольные программы хоть и играют достаточно важную роль, но практически не развиваются.  
+
+    Широкое развитие консольные программы получили в UNIX-подобных операционных системах, где консольные инструменты развиваются и совершенствуются до сих пор.
+
+- оконные приложения позволяют выводить информацию посредством растровых изображений с интенсивным использованием событийной модели. 
+    >Историческая справка - Xerox, Apple (Lisa, Macintosh), Microsoft (Windows), UNIX (X Window System). 
+
+    В настоящий момент используется два типа графических операционных систем: клиент-серверная (X Window) - приложение использует запрос к серверу X Window нарисовать что-то в определённой области и графическое ядро (Windows) - программа взаимодействует с операционной системой посредством Windows API, выступая по сути частью операционной системы. 
+    
+    Остановимся на оконных приложениях Windows. Все видимые элементы рассматриваются как окна, которые могут быть главным и дочерним (элементы управления). При создании окна приложения регистрируется функция окна, где происходит обработка событий. 
+    
+    Разработка при помощи Windows API требует определённых усилий и выполнения рутинных операций, поэтому было разработано несколько библиотек-обёрток, облегчающих построение Windows-приложений. Среди них следует отметить разработку Microsoft - MFC (очень сильно перекликается с API) и разработку Borland - VCL - более удобная, но менее гибкая система построения оконных приложений. 
+    
+    Достаточно популярность платформа .NET (изначально написна для Windows, но есть и реализация под Linux - Mono), в которой построением приложений занимается интерпретатор байт-кода - это позволяет ещё быстрее строить оконные приложения (недостаток - несовместимость и необходимость установки среды исполнения .NET-приложений).
+
+    QT (кьют) - кросс-платформенный фреймфорк, изначально разработан для С++, но есть привязки и ко многим другим языкам программирования, в частности для питона есть модуль PyQT.
+
+>Можно еще провести классификацию по принципу построения:
+>
+>* Standalone-приложение («Stand» и «Alone», что на русский дословно переводится как «остаться одному») — это такое программное обеспечение, которое не нуждается в каких-либо дополнительных программах и зависимостях для его установки и функционирования. 
+>* Клиент-серверное приложение, программа состоит из двух и более частей, у клиента на компьютере тонкий клиент, обеспечивающий только взаимодействие с пользователем и локальной периферией, а вся бизнес-логика вынесена на отдельный сервер.
+>* WEB-приложение (в принципе это частный случай клиент-серверного приложения, в котором клиенской части просто нет, вместо нее выступает браузер)
+
+## Консольные приложения
+
+Теория по консольным приложениям выше уже была, посмотрим как консольное приложение выглядит на C#:
+
+```cs
+namespace oap
+{
+    class Hello
+    {
+        static void Main(string[] args)
+        {
+            Console.WriteLine("Как Вас зовут?");
+            var Name = Console.ReadLine();
+            Console.WriteLine($"Привет {Name}");
+        }
+    }
+}
+```
+
+---
+
+**КОНТРОЛЬНЫЕ ВОПРОСЫ**
+
+* [Методы программирования](#методы-программирования)
+* [Общие принципы разработки программного обеспечения](#общие-принципы-разработки-программного-обеспечения)
+* [Жизненный цикл программного обеспечения](#жизненный-цикл-программного-обеспечения)
+* [Типы приложений](#типы-приложений)
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Языки программирования](./t2l1.md) | [Содержание](../readme.md#тема-2-языки-и-методы-программирования) | [Системы контроля версий](./skv.md)

+ 1012 - 0
articles/t3l1.md

@@ -0,0 +1,1012 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Системы контроля версий.](./skv.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Операторы и операции языка](./t3l1_2.md)
+
+# Основы языка `C#`
+
+<!-- https://professorweb.ru/my/csharp/charp_theory/level1/index.php -->
+<!-- https://docs.microsoft.com/ru-ru/dotnet/csharp/ -->
+
+* [Пространство имен](#пространство-имен)
+* [Комментарии](#комментарии)
+* [Документация XML](#документация-xml)
+* [Переменные](#переменные)
+* [Типы данных](#типы-данных)
+* [Преобразования типов](#преобразования-типов)
+* [Оператор присваивания](#оператор-присваивания)
+* [Операции с числами](#операции-с-числами)
+
+## Пространство имен
+
+**Namespace** буквально переводится как "пространство имён". Пространство имен предназначено для обеспечения возможности сохранения одного набора имен отдельно от другого. Названия классов, объявленные в одном пространстве имен, не конфликтуют с теми же именами классов, которые были объявлены в другом.
+
+Определение пространства имен начинается с ключевого слова **namespace**, за которым следует имя пространства имён:
+
+```cs
+namespace namespace_name {
+   // ваш код
+}
+```
+
+Чтобы вызвать класс или функцию, используя пространство имен, добавьте их **namespace**:
+
+```cs
+System.Console.WriteLine();
+```
+
+Здесь **System** и **Console** это пространства имен, а **WriteLine** функция, объявленная в этом пространстве имён.
+
+Если объекты из какого-то пространства имен используются очень часто, то можно "подключить" это пространство имен с помощью ключевого слова **using**:
+
+```cs
+using System.Console;
+...
+WriteLine();
+```
+
+## Комментарии
+
+Комментарии являются немаловажной частью любого языка программирования, т.к. позволяют удобно пояснять различные участки кода. В **C#** используются традиционные комментарии в стиле **Си** — однострочные (`//...`) и многострочные (`/* ... */`):
+
+```cs
+// Это однострочный комментарий
+/* Это уже
+многострочный комментарий */
+```
+
+Все, что находится в однострочном комментарии — от `//` до конца строки — игнорируется компилятором, как и весь многострочный комментарий, расположенный между `/*` и `*/`. Очевидно, что в многострочном комментарии не может присутствовать комбинация `*/`, поскольку она будет трактоваться как конец комментария.
+
+Многострочный комментарий можно помещать в одну строку кода:
+
+```cs
+WriteLine (/* Здесь идет комментарий! */ "Это скомпилируется");
+```
+
+Встроенные комментарии вроде этого нужно применять осторожно, потому что они могут ухудшить читабельность кода. Однако они удобны при отладке, скажем, когда необходимо временно попробовать запустить программу с указанным другим значением:
+
+```cs
+DoSomethingMethod (Width, /*Height*/ 100);
+```
+
+Символы комментария, включенные в строковый литерал, конечно же, трактуются как обычные символы:
+
+```cs
+string s = "/* Это просто нормальная строка */";
+```
+
+## Документация XML
+
+В дополнение к комментариям в стиле **Cи**, проиллюстрированным выше, в **C#** имеется очень искусное средство, на которое я хочу обратить особое внимание: способность генерировать документацию в формате **XML** на основе специальных комментариев. Это однострочные комментарии, начинающиеся с трех слешей (`///`) вместо двух. В таких комментариях можно размещать **XML**-дескрипторы, содержащие документацию по типам и членам типов, используемым в коде.
+
+**XML**-дескрипторы, распознаваемые компилятором, перечислены в следующей таблице:
+
+Дескриптор | Описание
+-----------|-----------
+`<c>` | Помечает текст в строке как код
+`<code>` | Помечает множество строк как код
+`<example>` | Помечает пример кода
+`<exception>` | Документирует класс исключения (синтаксис проверяется компилятором)
+`<include>` | Включает комментарии из другого файла документации (синтаксис проверяется компилятором)
+`<list>` | Вставляет список в документацию
+`<param>` | Помечает параметр метода (синтаксис проверяется компилятором)
+`<paramref>` | Указывает, что слово является параметром метода (синтаксис проверяется компилятором)
+`<permission>` | Документирует доступ к члену (синтаксис проверяется компилятором)
+`<remarks>` | Добавляет описание члена
+`<returns>` | Документирует возвращаемое методом значение
+`<see>` | Представляет перекрестную ссылку на другой параметр (синтаксис проверяется компилятором)
+`<seealso>` | Представляет раздел "see also" ("смотреть также") в описании (синтаксис проверяется компилятором)
+`<summary>` | Представляет краткий итог о типе или члене
+`<value>` | Описывает свойство
+
+Чтобы увидеть, как это работает, рассмотрим пример кода, в который добавим некоторые **XML**-комментарии:
+
+```cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ConsoleApplication1
+{
+    /// <summary>
+    ///  Класс Program
+    ///  основной класс программы
+    ///  выводящий текст "Hello, World!"
+    /// </summary>
+    class Program
+    {
+        /// <summary>
+        /// Метод Main() является
+        /// входной точкой работы программы
+        /// </summary>
+        /// <param name="args">Параметры метода Main()</param>
+        static void Main(string[] args)
+        {
+            // Форматируем шапку программы
+            Console.BackgroundColor = ConsoleColor.Green;
+            Console.ForegroundColor = ConsoleColor.Black;
+            Console.WriteLine("********************");
+            Console.WriteLine("**** Мой проект ****");
+            Console.WriteLine("********************");
+            // Основная программа
+            Console.BackgroundColor = ConsoleColor.Black;
+            Console.ForegroundColor = ConsoleColor.Green;
+            Console.WriteLine();
+            Console.WriteLine("Hello, World!");
+
+            // Ожидание нажатия клавиши Enter перед завершением работы
+            Console.ReadLine();
+        }
+    }
+}
+```
+
+Компилятор **C#** может извлекать **XML**-элементы из специальных комментариев и использовать их для генерации файлов **XML**. Чтобы заставить компилятор сгенерировать **XML**-документацию для сборки, указывается опция `/doc` вместе с именем файла, который должен быть создан:
+
+```
+csc /t:library /doc:MyApplication.xml MyApplication.cs
+```
+
+Данная команда сгенерирует файл **XML** с именем `MyApplication.xml` со следующим содержимым:
+
+```xml
+<?xml version="1.0"?>
+<doc>
+    <assembly>
+        <name>Program
+    </assembly>
+    <members>
+        <member name="T:ConsoleApplication1.Program">
+            <summary>
+             Класс Program
+             основной класс программы
+             выводящий текст "Hello, World!"
+            </summary>
+        </member>
+        <member name="M:ConsoleApplication1.Program.Main(System.String[])">
+            <summary>
+            Метод Main() является
+            входной точкой работы программы
+            </summary>
+            <param name="args">Аргумент метода Main()</param>
+        </member>
+    </members>
+</doc>
+```
+
+Обратите внимание на то, что компилятор на самом деле выполнил некоторую работу за вас: он создал элемент `<assembly>` и также добавил элементы `<member>` для каждого члена класса в этом файле. Каждый элемент `<member>` имеет атрибут name с полным именем члена, снабженным префиксом — буквой, который указывает на то, является он типом (`Т:`), полем (`F:`) или членом (`М:`).
+
+## Переменные
+
+Для хранения данных в программе в **C#**, как и в других языках программирования, применяются переменные. Переменная представляет именованный участок памяти, который хранит некоторое значение.
+
+В **C#** существуют две разновидности типов: **ссылочные** типы и типы **значений**. Переменные типа **значений** содержат непосредственно данные, а в переменных **ссылочных** типов хранятся ссылки на нужные данные, которые именуются объектами. Две переменные **ссылочного** типа могут ссылаться на один и тот же объект, поэтому может случиться так, что операции над одной переменной затронут объект, на который ссылается другая переменная. Каждая переменная типа **значения** имеет собственную копию данных, и операции над одной переменной не могут затрагивать другую (за исключением переменных параметров **ref** и **out**).
+
+Каждая переменная характеризуется определенным **именем**, **типом данных** и **значением**. **Имя переменной** представляет поизвольный идентификатор, который может содержать алфавитно-цифровые символы или символ подчеркивания и должен начинаться либо с алфавитного символа, либо со знака подчеркивания. 
+
+Синтаксис объявления переменных в **C#** выглядит следующим образом:
+
+```cs
+ТипДанных Идентификатор;
+```
+
+Например, определим переменную _age_:
+
+```cs
+int age;
+```
+
+Объявить можно переменную любого действительного типа. Важно подчеркнуть, что возможности переменной определяются ее типом. Например, переменную типа **bool** нельзя использовать для хранения числовых значений с плавающей точкой. Кроме того, тип переменной нельзя изменять в течение срока ее существования. В частности, переменную типа **int** нельзя преобразовать в переменную типа **char**.
+
+Все переменные в **C#** должны быть объявлены до их применения. Это нужно для того, чтобы уведомить компилятор о типе данных, хранящихся в переменной, прежде чем он попытается правильно скомпилировать любой оператор, в котором используется переменная. Это позволяет также осуществлять строгий контроль типов в **C#**.
+
+### Инициализация переменной
+
+Задать значение переменной можно, в частности, с помощью оператора присваивания. Кроме того, задать начальное значение переменной можно при ее объявлении. Для этого после имени переменной указывается знак равенства (`=`) и присваиваемое значение. Если две или более переменные одного и того же типа объявляются списком, разделяемым запятыми, то этим переменным можно задать, например, начальное значение. Ниже приведена общая форма инициализации переменной:
+
+```cs
+// задаем целочисленной переменной i значение 10
+int i = 10;
+
+// инициализируем переменную symbol буквенным значением Z
+char symbol = 'Z';
+
+// переменная f инициализируется числовым значением 15.7
+float f = 15.7F;     
+
+// инициализируем несколько переменных одного типа
+int x = 5, y = 10, z = 12;    
+```
+
+Инициализация переменных демонстрирует пример обеспечения безопасности **C#**. Коротко говоря, компилятор **C#** требует, чтобы любая переменная была инициализирована некоторым начальным значением, прежде чем можно было обратиться к ней в какой-то операции. В большинстве современных компиляторов нарушение этого правила определяется и выдается соответствующее предупреждение, но "всевидящий" компилятор **C#** трактует такие нарушения как ошибки. Это предохраняет от нечаянного получения значений "мусора" из памяти, оставшегося там от других программ.
+
+В **C#** используются два метода для обеспечения инициализации переменных перед пользованием:
+
+* Переменные, являющиеся **полями класса или структуры**, если не инициализированы явно, по умолчанию обнуляются в момент создания.
+* Переменные, **локальные по отношению к методу**, должны быть явно инициализированы в коде до появления любого оператора, в котором используются их значения. В данном случае при объявлении переменной ее инициализация не происходит автоматически, но компилятор проверит все возможные пути потока управления в методе и сообщит об ошибке, если обнаружит любую возможность использования значения этой локальной переменной до ее инициализации.
+
+### Динамическая инициализация
+
+В приведенных выше примерах в качестве инициализаторов переменных использовались константы, но в **C#** допускается также динамическая инициализация переменных с помощью любого выражения, действительного на момент объявления переменной:
+
+```cs
+int i1 = 3, i2 = 4;
+
+// Инициализируем динамически переменную result
+double result = Math.Sqrt(i1*i1 + i2*i2);
+```
+
+В данном примере объявляются три локальные переменные *i1*, *i2*, *result*, первые две из которых инициализируются константами, а переменная _result_ инициализируется динамически с использованием метода `Math.Sqrt()`, возвращающего квадратный корень выражения. Следует особо подчеркнуть, что в выражении для инициализации можно использовать любой элемент, действительный на момент самой инициализации переменной, в том числе вызовы методов, другие переменные или литералы.
+
+### Неявно типизированные переменные
+
+Как пояснялось выше, все переменные в **C#** должны быть объявлены. Как правило, при объявлении переменной сначала указывается тип, например **int** или **bool**, а затем имя переменной. Но начиная с версии **C#** 3.0, компилятору предоставляется возможность самому определить тип локальной переменной, исходя из значения, которым она инициализируется. Такая переменная называется **неявно типизированной**.
+
+Неявно типизированная переменная объявляется с помощью ключевого слова **var** и должна быть непременно инициализирована. Для определения типа этой переменной компилятору служит тип её инициализатора, т.е. значения, которым она инициализируется:
+
+```cs
+// переменная i инициализируется целочисленным литералом
+var i = 12;
+
+// переменная d инициализируется литералом с плавающей точкой,имеющему тип double
+var d = 12.3;
+
+// переменная f имеет тип float
+var f = 0.34F;
+```
+
+Единственное отличие **неявно типизированной** переменной от обычной, явно типизированной переменной, — в способе определения ее типа. Как только этот тип будет определен, он закрепляется за переменной до конца ее существования.
+
+Неявно типизированные переменные внедрены в **C#** не для того, чтобы заменить собой обычные объявления переменных. Напротив, неявно типизированные переменные предназначены для особых случаев, и самый примечательный из них имеет отношение к языку интегрированных запросов (**LINQ**). Таким образом, большинство объявлений переменных должно и впредь оставаться явно типизированными, поскольку они облегчают чтение и понимание исходного текста программы.
+
+### Константы
+
+Как следует из названия, **константа** — это переменная, значение которой не меняется за время ее существования. Предваряя переменную ключевым словом **const** при ее объявлении и инициализации, вы объявляете ее как константу:
+
+```cs
+// Это значение не может быть изменено
+const int a = 100; 
+```
+
+Ниже перечислены основные характеристики констант:
+
+* Они должны инициализироваться при объявлении, и однажды присвоенные им значения никогда не могут быть изменены.
+* Значение константы должно быть вычислено во время компиляции. Таким образом, инициализировать константу значением, взятым из другой переменной, нельзя. Если все-таки нужно это сделать, используйте поля только для чтения.
+* Константы всегда неявно статические. Однако вы не должны (и фактически не можете) включать модификатор **static** в объявление константы.
+
+Использование констант в программах обеспечивает, по крайней мере, три преимущества:
+
+* Константы облегчают чтение программ, заменяя "магические" числа и строки читаемыми именами, назначение которых легко понять.
+* Константы облегчают модификацию программ. Например, предположим, что в программе имеется константа *SalesTax* (налог с продаж), которой присвоено значение `6` процентов. Если налог с продаж когда-нибудь изменится, вы можете модифицировать все вычисления налога, просто присвоив новое значение этой константе, и не понадобится просматривать код в поисках значений и изменять каждое из них, надеясь, что оно нигде не будет пропущено.
+* Константы позволяют избежать ошибок в программах. Если попытаться присвоить новое значение константе где-то в другом месте программы, а не там, где она объявлена, компилятор выдаст сообщение об ошибке.
+
+## Типы данных
+
+Типы данных имеют особенное значение в **C#**, поскольку это строго типизированный язык. Это означает, что все операции подвергаются строгому контролю со стороны компилятора на соответствие типов, причем недопустимые операции не компилируются. Следовательно, строгий контроль типов позволяет исключить ошибки и повысить надежность программ. Для обеспечения контроля типов все переменные, выражения и значения должны принадлежать к определенному типу. Такого понятия, как "бестиповая" переменная, в данном языке программирования вообще не существует. Более того, тип значения определяет те операции, которые разрешается выполнять над ним. Операция, разрешенная для одного типа данных, может оказаться недопустимой для другого.
+
+В **C#** имеются две общие категории встроенных типов данных: типы **значений** и **ссылочные** типы. Они отличаются по содержимому переменной. Концептуально разница между ними состоит в том, что тип **значения** (value type) хранит данные непосредственно, в то время как **ссылочный** тип (reference type) хранит ссылку на значение.
+
+Эти типы сохраняются в разных местах памяти: типы **значений** сохраняются в области, известной как **стек**, а **ссылочные** типы — в области, называемой управляемой **кучей**.
+
+![](../img/03001.png)
+
+Давайте разберем типы значений.
+
+### Целочисленные типы
+
+В **C#** определены девять целочисленных типов: **char**, **byte**, **sbyte**, **short**, **ushort**, **int**, **uint**, **long** и **ulong**. Но тип **char** применяется, главным образом, для представления символов и поэтому рассматривается отдельно. Остальные восемь целочисленных типов предназначены для числовых расчетов. Ниже представлены их диапазон представления чисел и разрядность в битах:
+
+Тип  | Тип CTS     | Разрядность в битах | Диапазон
+-----|-------------|:-------------------:|---------
+byte | System.Byte | 8                   | 0..255
+sbyte| System.SByte | 8                  | -128..127
+short| System.Int16 | 16                 | -32768..32767
+ushort| System.UInt16 | 16               | 0..65535
+int  | System.Int32 | 32                 | -2147483648..2147483647
+uint | System.UInt32 | 32 | 0..4294967295
+long | System.Int64 | 64 | -9223372036854775808..9223372036854775807
+ulong| System.UInt64 | 64 | 0..18446744073709551615
+
+Как следует из приведенной выше таблицы, в **C#** определены оба варианта различных целочисленных типов: со знаком и без знака. Целочисленные типы со знаком отличаются от аналогичных типов без знака способом интерпретации старшего разряда целого числа. Так, если в программе указано целочисленное значение со знаком, то компилятор **C#** сгенерирует код, в котором старший разряд целого числа используется в качестве флага знака. Число считается положительным, если флаг знака равен `0`, и отрицательным, если он равен `1`.
+
+Вероятно, самым распространенным в программировании целочисленным типом является тип **int**. Переменные типа **int** нередко используются для управления циклами, индексирования массивов и математических расчетов общего назначения. Когда же требуется целочисленное значение с большим диапазоном представления чисел, чем у типа **int**, то для этой цели имеется целый ряд других целочисленных типов.
+
+Так, если значение нужно сохранить без знака, то для него можно выбрать тип **uint**, для больших значений со знаком — тип **long**, а для больших значений без знака — тип **ulong**. В качестве примера ниже приведена программа, вычисляющая расстояние от Земли до Солнца в сантиметрах. Для хранения столь большого значения в ней используется переменная типа **long**:
+
+```cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ConsoleApplication1
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            long result;
+            const long km = 149800000; // расстояние в км.
+
+            result = km * 1000 * 100;
+            Console.WriteLine(result);
+            Console.ReadLine();
+        }
+    }
+}
+```
+
+Всем целочисленным переменным значения могут присваиваться в десятичной или шестнадцатеричной системе обозначений. В последнем случае требуется префикс `0x`:
+
+```cs
+long x = 0x12ab;
+```
+
+Если возникает какая-то неопределенность относительно того, имеет ли целое значение тип **int**, **uint**, **long** или **ulong**, то по умолчанию принимается **int**. Чтобы явно специфицировать, какой другой целочисленный тип должно иметь значение, к числу можно добавлять следующие символы:
+
+```cs
+uint ui = 1234U;
+long l = 1234L;
+ulong ul = 1234UL;
+```
+
+>`U` и `L` можно также указывать в нижнем регистре, хотя строчную `L` легко зрительно спутать с цифрой `1` (единица).
+
+### Типы с плавающей точкой
+
+Типы с плавающей точкой позволяют представлять числа с дробной частью. В **C#** имеются две разновидности типов данных с плавающей точкой: **float** и **double**. Они представляют числовые значения с одинарной и двойной точностью соответственно. Так, разрядность типа **float** составляет 32 бита, что приближенно соответствует диапазону представления чисел от `5E-45` до `3,4E+38`. А разрядность типа **double** составляет 64 бита, что приближенно соответствует диапазону представления чисел от `5E-324` до `1,7Е+308`.
+
+Тип данных **float** предназначен для меньших значений с плавающей точкой, для которых требуется меньшая точность. Тип данных **double** больше, чем **float**, и предлагает более высокую степень точности (15 разрядов).
+
+Если нецелочисленное значение жестко кодируется в исходном тексте (например, `12.3`), то обычно компилятор предполагает, что подразумевается значение типа **double**. Если значение необходимо специфицировать как **float**, потребуется добавить к нему символ `F` (или `f`):
+
+```cs
+float f = 12.3F;
+```
+
+### Десятичный тип данных
+
+Для представления чисел с плавающей точкой высокой точности предусмотрен также десятичный тип **decimal**, который предназначен для применения в финансовых расчетах. Этот тип имеет разрядность **128 бит** для представления числовых значений в пределах от `1Е-28` до `7,9Е+28`. Вам, вероятно, известно, что для обычных арифметических вычислений с плавающей точкой характерны ошибки округления десятичных значений. Эти ошибки исключаются при использовании типа **decimal**, который позволяет представить числа с точностью до 28 (а иногда и 29) десятичных разрядов. Благодаря тому что этот тип данных способен представлять десятичные значения без ошибок округления, он особенно удобен для расчетов, связанных с финансами:
+
+```cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ConsoleApplication1
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            // *** Расчет стоимости капиталовложения с ***
+            // *** фиксированной нормой прибыли***
+            decimal money, percent;
+            int i;
+            const byte years = 15;
+
+            money = 1000.0m;
+            percent = 0.045m;
+
+            for (i = 1; i <= years; i++)
+            {
+                money *= 1 + percent;
+            }
+
+            Console.WriteLine("Общий доход за {0} лет: {1} $$",years,money);
+            Console.ReadLine();
+        }
+    }
+}
+```
+
+Результатом работы данной программы будет:
+
+![](../img/03002.png)
+
+### Символы
+
+В **C#** символы представлены не 8-разрядным кодом, как во многих других языках программирования, например **С++**, а 16-разрядным кодом, который называется юникодом (Unicode). В юникоде набор символов представлен настолько широко, что он охватывает символы практически из всех естественных языков на свете. Если для многих естественных языков, в том числе английского, французского и немецкого, характерны относительно небольшие алфавиты, то в ряде других языков, например китайском, употребляются довольно обширные наборы символов, которые нельзя представить 8-разрядным кодом. Для преодоления этого ограничения в **C#** определен тип **char**, представляющий 16-разрядные значения без знака в пределах от `0` до `65 535`. При этом стандартный набор символов в 8-разрядном коде **ASCII** является подмножеством юникода в пределах от 0 до 127. Следовательно, символы в коде **ASCII** по-прежнему остаются действительными в **C#**.
+
+Для того чтобы присвоить значение символьной переменной, достаточно заключить это значение (т.е. символ) в одинарные кавычки:
+
+```cs
+char ch;
+ch = 'Z';
+```
+
+Несмотря на то что тип **char** определен в **C#** как целочисленный, его не следует путать со всеми остальными целочисленными типами. Дело в том, что в **C#** отсутствует автоматическое преобразование символьных значений в целочисленные и обратно. Например, следующий фрагмент кода содержит ошибку:
+
+```cs
+char ch;
+ch = 8; // ошибка, не выйдет
+```
+
+Наравне с представлением **char** как символьных литералов, их можно представлять как 4-разрядные шестнадцатеричные значения Unicode (например, `\u0041`), целочисленные значения с приведением (например, `(char) 65`) или же шестнадцатеричные значения (например, `\x0041`). Кроме того, они могут быть представлены в виде [управляющих последовательностей.](#Управляющие_последовательности_символов)
+
+### Логический тип данных
+
+Тип **bool** представляет два логических значения: "истина" и "ложь". Эти логические значения обозначаются в **C#** зарезервированными словами `true` и `false` соответственно. Следовательно, переменная или выражение типа **bool** будет принимать одно из этих логических значений. Кроме того, в **C#** не определено взаимное преобразование логических и целых значений. Например, `1` не преобразуется в значение `true`, а `0` — в значение `false`.
+
+### Литералы
+
+В **C#** литералами называются постоянные значения, представленные в удобной для восприятия форме. Например, число `100` является литералом. Сами литералы и их назначение настолько понятны, что они применялись во всех предыдущих примерах программ без всяких пояснений. Но теперь настало время дать им формальное объяснение.
+
+В **C#** литералы могут быть любого простого типа. Представление каждого литерала зависит от конкретного типа. Как пояснялось ранее, символьные литералы заключаются в одинарные кавычки. Например, `'а'` и `'%'` являются символьными литералами.
+
+Целочисленные литералы указываются в виде чисел без дробной части. Например, `10` и `-100` — это целочисленные литералы. Для обозначения литералов с плавающей точкой требуется указывать десятичную точку и дробную часть числа. Например, `11.123` — это литерал с плавающей точкой. Для вещественных чисел с плавающей точкой в **C#** допускается также использовать экспоненциальное представление.
+
+У литералов должен быть также конкретный тип, поскольку **C#** является строго типизированным языком. В этой связи возникает естественный вопрос: к какому типу следует отнести числовой литерал, например `2`, `12 3987` или `0.23`? К счастью, для ответа на этот вопрос в **C#** установлен ряд простых для соблюдения правил:
+
+* У целочисленных литералов должен быть самый мелкий целочисленный тип, которым они могут быть представлены, начиная с типа **int**. Таким образом, у целочисленных литералов может быть один из следующих типов: **int**, **uint**, **long** или **ulong** в зависимости от значения литерала.
+* Литералы с плавающей точкой относятся к типу **double**.
+
+>Если вас не устраивает используемый по умолчанию тип литерала, вы можете явно указать другой его тип с помощью суффикса.
+
+Так, для указания типа **long** к литералу присоединяется суффикс `l` или `L`. Например, `12` — это литерал типа **int**, a `12L` — литерал типа **long**. Для указания целочисленного типа без знака к литералу присоединяется суффикс `u` или `U`. Следовательно, `100` — это литерал типа **int**, a `100U` — литерал типа **uint**. А для указания длинного целочисленного типа без знака к литералу присоединяется суффикс `ul` или `UL`. Например, `984375UL` — это литерал типа **ulong**.
+
+Кроме того, для указания типа **float** к литералу присоединяется суффикс `F` или `f`. Например, `10.19F` — это литерал типа **float**. Можете даже указать тип **double**, присоединив к литералу суффикс `d` или `D`, хотя это излишне. Ведь, как упоминалось выше, по умолчанию литералы с плавающей точкой относятся к типу **double**.
+
+И наконец, для указания типа **decimal** к литералу присоединяется суффикс `m` или `М`. Например, `9.95М` — это десятичный литерал типа **decimal**.
+
+Несмотря на то что целочисленные литералы образуют по умолчанию значения типа **int**, **uint**, **long** или **ulong**, их можно присваивать переменным типа **byte**, **sbyte**, **short** или **ushort**, при условии, что присваиваемое значение может быть представлено целевым типом.
+
+### Шестнадцатеричные литералы
+
+Вам, вероятно, известно, что в программировании иногда оказывается проще пользоваться системой счисления по основанию `16`, чем по основанию `10`. 
+
+Система счисления по основанию `16` называется шестнадцатеричной. В ней используются числа от `0` до `9`, а также буквы от `А` до `F`, которыми обозначаются десятичные числа `10`, `11`, `12`, `13`, `14` и `15`. Например, десятичному числу `16` соответствует шестнадцатеричное число `0x10`. Вследствие того что шестнадцатеричные числа применяются в программировании довольно часто, в **C#** разрешается указывать целочисленные литералы в шестнадцатеричном формате. Шестнадцатеричные литералы должны начинаться с символов `0x`, т.е. нуля и последующей латинской буквы "икс". Ниже приведены некоторые примеры шестнадцатеричных литералов:
+
+```cs
+count = 0xFF; // равно 255 в десятичной системе
+incr = 0x1a; // равно 26 в десятичной системе
+```
+
+### Управляющие последовательности символов
+
+Большинство печатаемых символов достаточно заключить в одинарные кавычки, но набор в текстовом редакторе некоторых символов, например возврата каретки, вызывает особые трудности. Кроме того, ряд других символов, в том числе одинарные и двойные кавычки, имеют специальное назначение в **C#**, поэтому их нельзя использовать непосредственно. По этим причинам в **C#** предусмотрены специальные управляющие последовательности символов:
+
+Управляющая<br/>последовательность | Описание
+:-----------------------------:|--------
+`\a` | Звуковой сигнал (звонок)
+`\b` | Возврат на одну позицию
+`\f` | Перевод страницы (переход на новую страницу)
+`\n` | Новая строка (перевод строки)
+`\r` | Возврат каретки
+`\t` | Горизонтальная табуляция
+`\v` | Вертикальная табуляция
+`\0` | Пустой символ
+`\'` | Одинарная кавычка
+`\"` | Двойная кавычка
+`\\` | Обратная косая черта
+
+### Строковые литералы
+
+В **C#** поддерживается еще один тип литералов — строковый. Строковый литерал представляет собой набор символов, заключенных в двойные кавычки. Например следующий фрагмент кода:
+
+```cs
+"This is text"
+```
+
+Помимо обычных символов, строковый литерал может содержать одну или несколько управляющих последовательностей символов, о которых речь шла выше. Также можно указать буквальный строковый литерал. Такой литерал начинается с символа `@`, после которого следует строка в кавычках. Содержимое строки в кавычках воспринимается без изменений и может быть расширено до двух и более строк. Это означает, что в буквальный строковый литерал можно включить символы новой строки, табуляции и прочие, не прибегая к управляющим последовательностям. Единственное исключение составляют двойные кавычки (`"`), для указания которых необходимо использовать двойные кавычки с обратным слэшем (`\`). Например:
+
+```cs
+// Используем перенос строки
+Console.WriteLine("Первая строка\nВторая строка\nТретья строка\n");
+
+// Используем вертикальную табуляцию
+Console.WriteLine("Первый столбец \v Второй столбец \v Третий столбец \n");
+
+// Используем горизонтальную табуляцию
+Console.WriteLine("One\tTwo\tThree");
+Console.WriteLine("Four\tFive\tSix\n");
+
+//Вставляем кавычки
+Console.WriteLine("\"Зачем?\", - спросил он");
+```
+
+### Нижние подчеркивания в числовых литералах
+
+Вы можете использовать нижние подчеркивания, чтобы сделать числовые константы более читаемыми:
+
+```cs
+var oneMillion = 1_000_000;
+var creditCardNumber = 1234_5678_9012_3456L;
+var socialSecurityNumber = 999_99_9999L;
+var hexBytes = 0xFF_EC_DE_5E;
+var bytes = 0b11010010_01101001_10010100_10010010;
+```
+
+## Преобразования типов
+
+В программировании нередко значения переменных одного типа присваиваются переменным другого типа. Например, в приведенном ниже фрагменте кода целое значение типа **int** присваивается переменной с плавающей точкой типа **float**:
+
+```cs
+int i;
+float f;
+i = 10;
+
+f = i; // присвоить целое значение переменной типа float
+```
+
+Если в одной операции присваивания смешиваются **совместимые** типы данных, то значение в правой части оператора присваивания автоматически преобразуется в тип, указанный в левой его части. Поэтому в приведенном выше фрагменте кода значение переменной `i` сначала преобразуется в тип **float**, а затем присваивается переменной `f`. Но вследствие строгого контроля типов далеко не все типы данных в **C#** оказываются полностью совместимыми, а следовательно, не все преобразования типов разрешены в неявном виде. Например, типы **bool** и **int** несовместимы. Правда, преобразование несовместимых типов все-таки может быть осуществлено путем приведения. Приведение типов, по существу, означает явное их преобразование.
+
+### Автоматическое преобразование типов
+
+Когда данные одного типа присваиваются переменной другого типа, неявное преобразование типов происходит автоматически при следующих условиях:
+
+* оба типа совместимы
+* диапазон представления чисел целевого типа шире, чем у исходного типа
+
+Если оба эти условия удовлетворяются, то происходит расширяющее преобразование. Например, тип **int** достаточно крупный, чтобы вмещать в себя все действительные значения типа **byte**, а кроме того, оба типа, **int** и **byte**, являются совместимыми целочисленными типами, и поэтому для них вполне возможно неявное преобразование.
+
+Числовые типы, как целочисленные, так и с плавающей точкой, вполне совместимы друг с другом для выполнения расширяющих преобразований. Рассмотрим пример:
+
+```cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ConsoleApplication1
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            short num1, num2;
+            num1 = 10;
+            num2 = 15;
+
+            Console.WriteLine("{0} + {1} = {2}",num1,num2,Sum(num1,num2));
+            Console.ReadLine();
+        }
+
+        static int Sum(int x, int y)
+        {
+            return x + y;
+        }
+    }
+}
+```
+
+Обратите внимание на то, что метод `Sum()` ожидает поступления двух параметров типа **int**. Тем не менее, в методе `Main()` ему на самом деле передаются две переменных типа **short**. Хотя это может показаться несоответствием типов, программа будет компилироваться и выполняться без ошибок и возвращать в результате, как и ожидалось, значение `25`.
+
+Причина, по которой компилятор будет считать данный код синтаксически корректным, связана с тем, что потеря данных здесь невозможна. Поскольку максимальное значение (`32 767`), которое может содержать тип **short**, вполне вписывается в рамки диапазона типа **int** (максимальное значение которого составляет `2 147 483 647`), компилятор будет неявным образом расширять каждую переменную типа **short** до типа **int**. Формально термин "расширение" применяется для обозначения неявного восходящего приведения (upward cast), которое не приводит к потере данных.
+
+### Приведение несовместимых типов
+
+Несмотря на всю полезность неявных преобразований типов, они неспособны удовлетворить все потребности в программировании, поскольку допускают лишь расширяющие преобразования совместимых типов. А во всех остальных случаях приходится обращаться к приведению типов. Приведение — это команда компилятору преобразовать результат вычисления выражения в указанный тип. А для этого требуется явное преобразование типов. Ниже приведена общая форма приведения типов:
+
+```
+(целевой_тип) выражение
+```
+
+Здесь **целевой_тип** обозначает тот тип, в который желательно преобразовать указанное выражение.
+
+Если приведение типов приводит к **сужающему** преобразованию, то часть информации может быть потеряна. Например, в результате приведения типа **long** к типу **int** часть информации потеряется, если значение типа **long** окажется больше диапазона представления чисел для типа **int**, поскольку старшие разряды этого числового значения отбрасываются. Когда же значение с плавающей точкой приводится к целочисленному, то в результате усечения теряется дробная часть этого числового значения. Так, если присвоить значение `1.23` целочисленной переменной, то в результате в ней останется лишь целая часть исходного числа (`1`), а дробная его часть (`0.23`) будет потеряна. Давайте рассмотрим пример:
+
+```cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ConsoleApplication1
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            int i1 = 455, i2 = 84500;
+            decimal dec = 7.98845m;
+
+            // Приводим два числа типа int
+            // к типу short
+            Console.WriteLine((short)i1);
+            Console.WriteLine((short)i2);
+
+            // Приводим число типа decimal
+            // к типу int
+            Console.WriteLine((int)dec);
+
+            Console.ReadLine();
+        }
+    }
+}
+```
+
+Результатом работы данной программы будет:
+
+![](../img/03003.png)
+
+Обратите внимание, что переменная `i1` корректно преобразовалась в тип **short**, т.к. ее значение входит в диапазон этого типа данных. Преобразование переменной `dec` в тип **int** вернуло целую часть этого числа. Преобразование переменной `i2` вернуло значение переполнения `18964` (т.е. 84500 - 2*32768).
+
+### Перехват сужающих преобразований данных
+
+В предыдущем примере приведение переменной `i2` к типу **short** не является приемлемым, т.к. возникает **потеря данных**. Для создания приложений, в которых потеря данных должна быть недопустимой, в **C#** предлагаются такие ключевые слова, как **checked** и **unchecked**, которые позволяют гарантировать, что потеря данных не окажется незамеченной.
+
+По умолчанию, в случае, когда не предпринимается никаких соответствующих исправительных мер, **условия переполнения (overflow)** и **потери значимости (underflow)** происходят без выдачи ошибки. Обрабатывать условия переполнения и потери значимости в приложении можно двумя способами. Это можно делать вручную, полагаясь на свои знания и навыки в области программирования.
+
+Недостаток такого подхода в том, что даже в случае приложения максимальных усилий человек все равно остается человеком, и какие-то ошибки могут ускользнуть от его глаз.
+
+К счастью, в **C#** предусмотрено ключевое слово **checked**. Если оператор (или блок операторов) заключен в контекст **checked**, компилятор **C#** генерирует дополнительные CIL-инструкции, обеспечивающие проверку на предмет условий переполнения, которые могут возникать в результате сложения, умножения, вычитания или деления двух числовых типов данных.
+
+В случае возникновения условия переполнения во время выполнения будет генерироваться исключение **System.OverflowException**. Давайте рассмотрим пример, в котором будем передавать в консоль значение исключения:
+
+```cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ConsoleApplication1
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            byte var1 = 250;
+            byte var2 = 150;
+            try
+            {
+                byte sum = checked((byte)(var1+var2));
+                Console.WriteLine("Сумма: {0}", sum);
+            }
+            catch (OverflowException ex)
+            {
+                Console.WriteLine(ex.Message);
+                Console.ReadLine();
+            }
+
+        }
+    }
+}
+```
+
+Результат работы данной программы:
+
+```sh
+Переполнение в результате выполнения арифметической операции.
+```
+
+### Настройка проверки на предмет возникновения условий переполнения в масштабах проекта
+
+Если создается приложение, в котором переполнение никогда не должно проходить незаметно, может выясниться, что обрамлять ключевым словом **checked** приходится раздражающе много строк кода. На такой случай в качестве альтернативного варианта в компиляторе **C#** поддерживается флаг `/checked`. При активизации этого флага проверки на предмет возможного переполнения будут автоматически подвергаться все имеющиеся в коде арифметические операции, без применения для каждой из них ключевого слова **checked**. Обнаружение переполнения точно так же приводит к генерации соответствующего исключения во время выполнения.
+
+Для активизации этого флага в **Visual Studio** необходимо открыть страницу свойств проекта (Проект - свойства), перейти на вкладку **Build** (Сборка), щелкнуть на кнопке **Advanced** (Дополнительно) и в открывшемся диалоговом окне отметить флажок *Check for arithmetic overflow/underflow* (Проверять арифметическое переполнение):
+
+![](../img/03017.png)
+
+Важно отметить, что в **C#** предусмотрено ключевое слово **unchecked**, которое позволяет отключить выдачу связанного с переполнением исключения в отдельных случаях.
+
+Итак, чтобы подвести итог по использованию в **C#** ключевых слов **checked** и **unchecked**, следует отметить, что по умолчанию арифметическое переполнение в исполняющей среде **.NET** игнорируется. Если необходимо обработать отдельные операторы, то должно использоваться ключевое слово **checked**, а если нужно перехватывать все связанные с переполнением ошибки в приложении, то понадобится активизировать флаг `/checked`. Что касается ключевого слова **unchecked**, то его можно применять при наличии блока кода, в котором переполнение является допустимым (и, следовательно, не должно приводить к генерации исключения во время выполнения).
+
+### Роль класса System.Convert
+
+В завершении темы преобразования типов данных стоит отметить, что в пространстве имен **System** имеется класс **Convert**, который тоже может применяться для расширения и сужения данных:
+
+```cs
+byte sum = Convert.ToByte(var1 + var2);
+```
+
+Однако, поскольку в **C#** есть операция явного преобразования, использование класса **Convert** для преобразования типов данных обычно является делом вкуса. :)
+
+## Оператор присваивания
+
+Оператор присваивания обозначается одиночным знаком равенства (`=`). В **C#** оператор присваивания действует таким же образом, как и в других языках программирования. Ниже приведена его общая форма:
+
+```
+имя_переменной = выражение
+```
+
+Здесь **имя_переменной** должно быть совместимо с типом выражения. У оператора присваивания имеется одна интересная особенность, о которой вам будет полезно знать: он позволяет создавать цепочку операций присваивания. Рассмотрим следующий фрагмент кода:
+
+```cs
+int x, у, z;
+x = у = z = 10; // присвоить значение 10 переменным x, у и z
+```
+
+В приведенном выше фрагменте кода одно и то же значение `10` задается для переменных `х`, `у` и `z` с помощью единственного оператора присваивания. Это значение присваивается сначала переменной `z`, затем переменной `у` и, наконец, переменной `х`. Такой способ присваивания "по цепочке" удобен для задания общего значения целой группе переменных.
+
+### Составные операторы присваивания
+
+В **C#** предусмотрены специальные составные операторы присваивания, упрощающие программирование некоторых операций присваивания. Обратимся сначала к простому примеру:
+
+```cs
+x = x + 1;
+
+// Можно переписать следующим образом
+x += 1;
+```
+
+Пара операторов `+=` указывает компилятору на то, что переменной `х` должно быть присвоено ее первоначальное значение, увеличенное на `1`.
+
+Для многих двоичных операций, т.е. операций, требующих наличия двух операндов, существуют отдельные составные операторы присваивания. Общая форма всех этих операторов имеет следующий вид:
+
+```
+имя_переменной op = выражение
+```
+
+где **op** — арифметический или логический оператор, применяемый вместе с оператором присваивания. Ниже перечислены составные операторы присваивания для арифметических и логических операций:
+
+Оператор | Аналог (выражение из вышеуказанного примера)
+:-------:|-----
+`+=` | x = x + 1;
+`-=` | x = x - 1;
+`*=` |  x = x * 1;
+`/=` | x = x / 1;
+`%=` | x = x % 1;
+`\|=` | x = x \| 1;
+`^=` | x = x ^ 1;
+
+Составные операторы присваивания записываются более кратко, чем их несоставные эквиваленты. Поэтому их иногда еще называют укороченными операторами присваивания.
+
+У составных операторов присваивания имеются два главных преимущества. Во-первых, они более компактны, чем их "несокращенные" эквиваленты. И во-вторых, они дают более эффективный исполняемый код, поскольку левый операнд этих операторов вычисляется только один раз. Именно по этим причинам составные операторы присваивания чаще всего применяются в программах, профессионально написанных на **C#**.
+
+## Операции с числами
+
+### Арифметические операции
+
+Арифметические операторы, представленные в **C#**, приведены ниже:
+
+Оператор | Действие
+:-------:|---------
+`+` | Сложение
+`-` | Вычитание, унарный минус
+`*` | Умножение
+`/` | Деление
+`%` | Деление по модулю
+`--` | Декремент
+`++` | Инкремент
+
+Операторы `+`,`-`,`*` и `/` действуют так, как предполагает их обозначение. Их можно применять к любому встроенному числовому типу данных.
+
+Действие арифметических операторов не требует особых пояснений, за исключением следующих особых случаев. Прежде всего, не следует забывать, что когда оператор `/` применяется к целому числу, то любой остаток от деления отбрасывается; например, результат целочисленного деления `13/3` будет равен `4`. Остаток от этого деления можно получить с помощью оператора деления по модулю (`%`), который иначе называется оператором вычисления остатка. Он дает остаток от целочисленного деления. Например, `13 % 3` равно `1`. В **C#** оператор `%` можно применять как к целочисленным типам данных, так и к типам с плавающей точкой. Поэтому `13.0 % 3.0` также равно `1`. В этом отношении **C#** отличается от языков `С` и `С++`, где операции деления по модулю разрешаются только для целочисленных типов данных. Давайте рассмотрим следующий пример:
+
+```cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ConsoleApplication1
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            int num1, num2;
+            float f1, f2;
+
+            num1 = 13 / 3;
+            num2 = 13 % 3;
+
+            f1 = 13.0f / 3.0f;
+            f2 = 13.0f % 3.0f;
+
+            Console.WriteLine("Результат и остаток от деления 13 на 3: {0} __ {1}",num1,num2);
+            Console.WriteLine("Результат деления 13.0 на 3.0: {0:#.###} {1}", f1, f2);
+
+            Console.ReadLine();
+        }
+    }
+}
+```
+
+Результат работы данной программы:
+
+![](../img/03005.png)
+
+#### Операторы инкремента и декремента
+
+Оператор инкремента (`++`) увеличивает свой операнд на `1`, а оператор декремента (`--`) уменьшает операнд на `1`. Следовательно, операторы:
+
+```cs
+x++;
+x--;
+```
+
+равнозначны операторам:
+
+```cs
+x = x + 1;
+x = x - 1;
+```
+
+Следует иметь в виду, что в инкрементной или декрементной форме значение переменной `x` вычисляется только один, а не два раза. В некоторых случаях это позволяет повысить эффективность выполнения программы.
+
+Обa оператора инкремента и декремента можно указывать до операнда (в префиксной форме) или же после операнда (в постфиксной форме). Давайте разберем разницу записи операции инкремента или декремента на примере:
+
+```cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ConsoleApplication1
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            short d = 1;
+
+            for (byte i = 0; i < 10; i++)
+                Console.Write(i + d++ + "\t");
+
+            Console.WriteLine();
+            d = 1;
+
+            for (byte i = 0; i < 10; i++)
+                Console.Write(i + ++d + "\t");
+
+            Console.ReadLine();
+        }
+
+    }
+}
+```
+
+![](../img/03006.png)
+
+Т.е. операция инкремента в префиксной форме происходит раньше, нежели в постфиксной форме, в результате чего числа из второго ряда получаются на единицу больше. Отмечу, что возможность управлять моментом инкремента или декремента дает немало преимуществ при программировании.
+
+### Поразрядные операторы
+
+В **C#** предусмотрен ряд **поразрядных операторов**, расширяющих круг задач, для решения которых можно применять **C#**. Поразрядные операторы воздействуют на отдельные двоичные разряды (биты) своих операндов. Они определены только для целочисленных операндов, поэтому их нельзя применять к данным типа **bool**, **float** или **double**.
+
+Эти операторы называются *поразрядными*, поскольку они служат для проверки, установки или сдвига двоичных разрядов, составляющих целое значение. Среди прочего поразрядные операторы применяются для решения самых разных задач программирования на уровне системы, включая, например, анализ информации состояния устройства. Все доступные в **C#** поразрядные операторы приведены ниже:
+
+Оператор | Значение
+:-------:|---
+`&` | Поразрядное И
+`\|` | Поразрядное ИЛИ
+`^` | Порязрядное исключающее ИЛИ
+`<<` | Сдвиг влево
+`>>` | Сдвиг вправо
+`~` | Дополнение до `1` (унарный оператор НЕ)
+
+### Поразрядные операторы И, ИЛИ, исключающее ИЛИ и НЕ
+
+Поразрядные операторы `И`, `ИЛИ`, `исключающее ИЛИ` и `НЕ` обозначаются следующим образом: `&`, `|`, `^` и `~`. Они выполняют те же функции, что и их логические аналоги. Но в отличие от логических операторов, поразрядные операторы действуют на уровне отдельных двоичных разрядов.
+
+С точки зрения наиболее распространенного применения поразрядную операцию `И` можно рассматривать как способ подавления отдельных двоичных разрядов. Это означает, что если какой-нибудь бит в любом из операндов равен `0`, то соответствующий бит результата будет сброшен в `0`. Поразрядный оператор `ИЛИ` может быть использован для установки отдельных двоичных разрядов. Если в `1` установлен какой-нибудь бит в любом из операндов этого оператора, то в `1` будет установлен и соответствующий бит в другом операнде. Поразрядный оператор `исключающее ИЛИ` устанавливает двоичный разряд операнда в том и только в том случае, если двоичные разряды сравниваемых операндов оказываются разными, как в приведенном ниже примере. Для понимания вышесказaнного, разберите следующий пример:
+
+![](../img/03009.png)
+
+Давайте теперь рассмотрим пример программы, использующей поразрядные операторы:
+
+```cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ConsoleApplication1
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            chet(16);
+            provChet(8);
+            nechet(16);
+
+            Console.ReadLine();
+        }
+
+        // Метод, преобразующий все нечетные числа в четные
+        // в диапазоне [0, x] c помощью 
+        // поразрядного оператора &
+        static void chet(int x)
+        {
+            int result;
+            Console.WriteLine("Преобразованный диапазон чисел от 0 до {0}:\n",x);
+            for (int i = 0; i <= x; i++)
+            {
+                // Сбрасываем младший разряд числа, чтобы
+                // получить четное число
+                result = i & 0xFFFE;
+                Console.Write("{0}\t",result);
+            }
+        }
+
+        // Метод, проверяющий является ли число четным
+        static void provChet(int x)
+        {
+            Console.WriteLine("\n\nПроверка четности чисел в диапазоне от 1 до {0}\n",x);
+            for (int i = 1; i <= x; i++)
+            {
+                if ((i & 1) == 0)
+                    Console.WriteLine("Число {0} - является четным",i); 
+                else
+                    Console.WriteLine("Число {0} - является нечетным",i);
+            }
+        }
+
+        // Метод, преобразующий четные числа в нечетные
+        // с помощью поразрядного оператора |
+        static void nechet(int x)
+        {
+            int result;
+            Console.WriteLine("\nПреобразованный диапазон чисел от 0 до {0}:\n",x);
+            for (int i = 0; i <= x; i++)
+            {
+                result = i | 1;
+                Console.Write("{0}\t",result);
+            }
+        }
+
+    }
+}
+```
+
+![](../img/03010.png)
+
+
+### Операторы сдвига
+
+В **C#** имеется возможность сдвигать двоичные разряды, составляющие целое значение, влево или вправо на заданную величину. Ниже приведена общая форма для этих операторов:
+
+```
+значение << число_битов
+значение >> число битов
+```
+
+где **число_битов** — это число двоичных разрядов, на которое сдвигается указанное значение.
+
+При сдвиге влево все двоичные разряды в указываемом значении сдвигаются на одну позицию влево, а младший разряд сбрасывается в нуль. При сдвиге вправо все двоичные разряды в указываемом значении сдвигаются на одну позицию вправо. Если вправо сдвигается целое значение без знака, то старший разряд сбрасывается в нуль. А если вправо сдвигается целое значение со знаком, то разряд знака сохраняется. Напомним, что для представления отрицательных чисел старший разряд целого числа устанавливается в `1`. Так, если сдвигаемое значение является отрицательным, то при каждом сдвиге вправо старший разряд числа устанавливается в `1`. А если сдвигаемое значение является положительным, то при каждом сдвиге вправо старший разряд числа сбрасывается в нуль.
+
+При сдвиге влево и вправо крайние двоичные разряды теряются. Восстановить потерянные при сдвиге двоичные разряды нельзя, поскольку сдвиг в данном случае не является циклическим. Рассмотрим пример:
+
+```cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ConsoleApplication1
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            byte n = 6, result;
+
+            // Умножить на 2
+            result = (byte)(n << 1);
+            Console.WriteLine("{0} * 2 = {1}",n,result);
+
+            // Умножить на 4
+            result = (byte)(n << 2);
+            Console.WriteLine("{0} * 4 = {1}",n,result);
+
+            // Разделить на 2
+            result = (byte)(n >> 1);
+            Console.WriteLine("{0} / 2 = {1}",n,result);
+
+            Console.ReadLine();
+        }
+    }
+}
+```
+
+![](../img/03011.png)
+
+---
+
+## КОНТРОЛЬНЫЕ ВОПРОСЫ:
+
+1. что такое пространство имен и для чего оно нужно?
+1. Какими символами обозначаются комментарии?
+1. Чем характеризуетя переменная?
+1. Какие типы данных по-умолчанию используются для целых чисел и для чисел с плавыющей запятой?
+1. За что отвечают ключевые слова **checked** и **unchecked**?
+
+---
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Системы контроля версий.](./skv.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Операторы и операции языка](./t3l1_2.md)

+ 599 - 0
articles/t3l1_2.md

@@ -0,0 +1,599 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Основные элементы языка.](./t3l1.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Массивы как структурированный тип данных.](./t3l1_3.md)
+
+# Основы языка `C#`
+
+**Содержание:**
+
+* [Операторы отношения и логические операторы](#Операторы_отношения_и_логические_операторы)
+* [Условные операторы](#Условные_операторы)
+* [Циклы](#Циклы)
+* [Операторы перехода](#Операторы_перехода)
+
+## Операторы отношения и логические операторы
+
+В обозначениях **оператор отношения** и **логический оператор** термин **отношения** означает взаимосвязь, которая может существовать между двумя значениями, а термин **логический** — взаимосвязь между логическими значениями "истина" и "ложь". И поскольку операторы отношения дают истинные или ложные результаты, то они нередко применяются вместе с логическими операторами. Именно по этой причине они и рассматриваются совместно.
+
+Ниже перечислены операторы отношения:
+
+Оператор | Значение
+:-------:|--------
+`==`     | Равно
+`!=`     | Не равно
+`>`      | Больше
+`<`      | Меньше
+`>=`     | Больше или равно
+`<=`     | Меньше или равно
+
+К числу логических относятся операторы, приведенные ниже:
+
+Оператор | Значение
+:--------:|-------
+`&` | И
+`\|` | ИЛИ
+`^` | Исключающее ИЛИ
+`&&` | Укороченное И
+`\|\|` | Укороченное ИЛИ
+`!` | НЕ
+
+Результатом выполнения оператора отношения или логического оператора является логическое значение типа **bool**.
+
+В целом, объекты можно сравнивать на равенство или неравенство, используя операторы отношения `==` и `!=`. А операторы сравнения `<`, `>`, `<=` или `>=` могут применяться только к тем типам данных, которые поддерживают отношение порядка. Следовательно, операторы отношения можно применять ко всем числовым типам данных. Но значения типа **bool** могут сравниваться только на равенство или неравенство, поскольку истинные (`true`) и ложные (`false`) значения не упорядочиваются. Например, сравнение `true > false` в `C#` не имеет смысла.
+
+Рассмотрим пример программы, демонстрирующий применение операторов отношения и логических операторов:
+
+```cs
+short d = 10, f = 12;
+bool var1 = true, var2 = false;
+
+if (d < f) 
+    Console.WriteLine("d < f");
+if (d <= f)
+    Console.WriteLine("d <= f");
+if (d != f)
+    Console.WriteLine("d != f");
+
+// Следующее условие не выполнится
+if (d > f)
+    Console.WriteLine("d > f");
+
+// Сравниванием переменные var1 и var2
+if (var1 & var2)
+    Console.WriteLine("Данный текст не выведется");
+if (!(var1 & var2))
+    Console.WriteLine("!(var1 & var2) = true");
+if (var1 | var2)
+    Console.WriteLine("var1 | var2 = true");
+if (var1 ^ var2)
+    Console.WriteLine("var1 ^ var2 = true");
+
+Console.ReadLine();
+```
+
+![](../img/03007.png)
+
+### Укороченные логические операторы
+
+В **C#** предусмотрены также специальные, *укороченные*, варианты логических операторов `И` и `ИЛИ`, предназначенные для получения более эффективного кода. Поясним это на следующих примерах логических операций. Если первый операнд логической операции `И` имеет ложное значение (`false`), то ее результат будет иметь ложное значение независимо от значения второго операнда. Если же первый операнд логической операции `ИЛИ` имеет истинное значение (`true`), то ее результат будет иметь истинное значение независимо от значения второго операнда. Благодаря тому что значение второго операнда в этих операциях вычислять не нужно, *экономится время и повышается эффективность кода*.
+
+Укороченная логическая операция `И` выполняется с помощью **оператора &&**, а укороченная логическая операция `ИЛИ` — с помощью **оператора ||**. Этим укороченным логическим операторам соответствуют обычные логические операторы `&` и `|`. Единственное отличие укороченного логического оператора от обычного заключается в том, что второй его операнд вычисляется только по мере необходимости.
+
+Укороченные логические операторы иногда оказываются более эффективными, чем их обычные аналоги. Так зачем же нужны обычные логические операторы `И` и `ИЛИ`? Дело в том, что в некоторых случаях требуется вычислять оба операнда логической операции `И` либо `ИЛИ` из-за возникающих побочных эффектов. Пример:
+
+
+В данном случае используется укороченный оператор и операции сравнения выполнится нормально
+
+```cs
+if (f != 0 && (d % f) == 0)
+    Console.WriteLine("{0} делится нацело на {1}",d,f);
+```
+
+В этом случае так же используется укороченный оператор но при этом возникнет исключительная ситуация т.к. первый оператор сравнения содержит деление на `0`
+
+```cs
+if ((d % f) == 0 && f != 0)
+    Console.WriteLine("{0} делится нацело на {1}", d, f);
+```
+
+При использовании целостного оператора `&` в любом случае возникнет исключительная ситуация, т.к. второе выражение будет вычислено несмотря на то, что первое выражение вернуло `false`
+
+```cs
+if (f != 0 & (d % f) == 0)
+    Console.WriteLine("{0} делится нацело на {1}", d, f);
+```
+
+Другой пример, когда в выражении изменяется переменная:
+
+При использовании обычного оператора `|`, в данной конструкции переменная `i` будет инкреминироваться
+
+```cs
+if (b | (++i < 10))
+    Console.WriteLine("i равно {0}", i);   // i = 1
+```
+
+При использовании укороченного оператора `||` значение `i` останется прежним
+
+```cs
+if (b || (++i < 10))
+    Console.WriteLine("i равно {0}", i);  // i = 0
+```
+
+Стоит отметить, что при возникновении исключительной ситуации во время отладки кода, IDE выводит сообщение следующего характера:
+
+![](../img/03008.png)
+
+## Условные операторы
+
+Условные операторы позволяют управлять *потоком выполнения* программы, чтобы не выполнялась каждая строка кода, как она следует в программе. Давайте рассмотрим все условные операторы языка `C#`:
+
+### Оператор if
+
+Для организации условного ветвления язык `C#` унаследовал от `С` и `С++` конструкцию `if...else`. Ее синтаксис должен быть интуитивно понятен для любого, кто программировал на процедурных языках:
+
+```cs
+if (условие)
+  оператор (операторы)
+else
+  оператор (операторы)
+```
+
+Если по каждому из условий нужно выполнить более одного оператора, эти операторы должны быть объединены в блок с помощью фигурных скобок `{...}`. (Это также касается других конструкций `C#`, в которых операторы могут быть объединены в блок — таких как циклы `for` и `while`.)
+
+Стоит обратить внимание, что в отличие от языков `С` и `С++`, в `C#` условный оператор `if` может работать только с булевскими выражениями, но не с произвольными значениями вроде `-1` и `0`.
+
+В операторе `if` могут применяться сложные выражения, и он может содержать операторы `else`, обеспечивая выполнение более сложных проверок. Синтаксис похож на применяемый в аналогичных ситуациях в языках `С` (`С++`) и `Java`. При построении сложных выражений в `C#` используется вполне ожидаемый набор логических операторов. Давайте рассмотрим следующий пример:
+
+```cs
+string myStr;
+Console.WriteLine("Введите строку: ");
+
+myStr = Console.ReadLine();
+
+if (myStr.Length < 5)
+    Console.WriteLine(
+        "\nВ данной строке меньше 5 символов");
+else if ((myStr.Length >= 5) && (myStr.Length <= 12)) 
+    Console.WriteLine(
+        "\nВ данной строке {0} символов",myStr.Length);
+else 
+    Console.WriteLine(
+        "\nВ данной строке больше 12 символов");
+
+Console.ReadLine();
+```
+
+![](../img/03013.png)
+
+Как видите количество `else if`, добавляемых к единственному `if`, не ограничено. Один момент, который следует отметить касательно `if`: фигурные скобки применять не обязательно, если в условной ветви присутствует только один оператор, как показано в исходном примере.
+
+### Оператор switch
+
+Вторым оператором выбора в `C#` является оператор **switch**, который обеспечивает многонаправленное ветвление программы. Следовательно, этот оператор позволяет сделать выбор среди нескольких альтернативных вариантов дальнейшего выполнения программы. Несмотря на то что многонаправленная проверка может быть организована с помощью последовательного ряда вложенных операторов `if`, во многих случаях более эффективным оказывается применение оператора **switch**. Этот оператор действует следующим образом. Значение выражения последовательно сравнивается с константами выбора из заданного списка. Как только будет обнаружено совпадение с одним из условий выбора, выполняется последовательность операторов, связанных с этим условием. Ниже приведена общая форма оператора **switch**:
+
+```cs
+switch(выражение) {
+    case константа1:
+        последовательность операторов
+        break;
+    case константа2:
+        последовательность операторов
+        break;
+    case константаЗ:
+        последовательность операторов
+        break;
+
+    ...
+
+    default:
+        последовательность операторов
+        break;
+}
+```
+
+Хотя оператор `switch...case` должен быть знаком программистам на `С` и `С++`, в `C#` он немного безопаснее, чем его эквивалент `С++`. В частности, он запрещает "сквозные" условия почти во всех случаях. Это значит, что если часть **case** вызывается в начале блока, то фрагменты кода за последующими частями **case** не могут быть выполнены, если только не используется явно оператор **goto** для перехода к ним. Компилятор обеспечивает это ограничение за счет того, что требует, чтобы за каждой частью **case** следовал оператор **break**, в противном случае он выдает ошибку.
+
+Важно отметить, что заданное выражение в операторе **switch** должно быть **целочисленного типа** (**char**, **byte**, **short** или **int**), **перечислимого** или же **строкового**. А выражения других типов, например с плавающей точкой, в операторе **switch** не допускаются. Зачастую выражение, управляющее оператором **switch**, просто сводится к одной переменной. Кроме того, константы выбора должны иметь тип, совместимый с типом выражения. В одном операторе **switch** не допускается наличие двух одинаковых по значению констант выбора.
+
+Давайте на примере рассмотрим использование оператора **switch**:
+
+```cs
+Console.WriteLine("Введите язык (C#, VB или C++)");
+string myLanguage = Console.ReadLine();
+
+sw1(myLanguage);
+
+Console.ReadLine();
+// Данный метод выводит выбор пользователя
+static void sw1(string s)
+{
+    switch (s)
+    {
+        case "C#":
+            Console.WriteLine("Вы выбрали язык C#");
+            break;
+        case "VB":
+            Console.WriteLine("Вы выбрали язык Visual Basic");
+            break;
+        case "C++":
+            Console.WriteLine("Вы выбрали язык С++");
+            break;
+        default:
+            Console.WriteLine("Такой язык я не знаю");
+            break;
+    }
+}
+```
+
+![](../img/03014.png)
+
+### Тернарный оператор
+
+Тернарный оператор (`?`) относится к числу самых примечательных в `C#`. Он представляет собой условный оператор и часто используется вместо определенных видов конструкций `if-else`. Ниже приведена общая форма этого оператора (в квадратных скобках не обязательная часть):
+
+```
+[var Result =] Условие ? Выражение_если_условие_истинно : Выражение_если_условие_ложно;
+```
+
+Здесь **Условие** должно относиться к типу **bool** а *Выражение_если_условие_истинно* и *Выражение_если_условие_ложно* — к одному и тому же типу. Обратите внимание на применение двоеточия и его местоположение в операторе `?`. Значение выражения `?` определяется следующим образом. Сначала вычисляется *Условие*. Если оно истинно, то вычисляется *Выражение_если_условие_истинно*, а полученный результат определяет значение всего выражения `?` в целом. Если же *Условие* оказывается ложным, то вычисляется *Выражение_если_условие_ложно*, и его значение становится общим для всего выражения `?`:
+
+```cs
+int b, c;
+c = -4;
+
+b = (c >= 0) ? c : c*c;
+```
+
+Присваивать переменной результат выполнения оператора `?` совсем не обязательно. Например, значение, которое дает оператор `?`, можно использовать в качестве аргумента при вызове метода. А если все выражения в операторе `?` относятся к типу **bool**, то такой оператор может заменить собой условное выражение в цикле или операторе `if`. Давайте рассмотрим пример использования тернарного оператора:
+
+```cs
+int result;
+
+// Реализуем функцию модуля числа
+for (int i = 5; i >= -5; i--)
+{
+    result = i >= 0 ? i : -i;
+    Console.Write("{0}\t", result);
+}
+
+Console.WriteLine("\n\n");
+
+// Выбор четных чисел
+for (int i = 0; i < 10; i++)
+{
+    if (i % 2 == 0 ? true : false)
+        Console.Write("{0}\t",i);
+}
+
+Console.ReadLine();
+```
+
+![](../img/03012.png)
+
+## Циклы
+
+В **C#** имеются четыре различных вида циклов (`for`, `while`, `do...while` и `foreach`), позволяющие выполнять блок кода повторно до тех пор, пока удовлетворяется определенное условие. В этой лекции мы познакомимся с циклами **for** и **while**.
+
+### Цикл for
+
+Цикл for в `C#` предоставляет механизм итерации, в котором определенное условие проверяется перед выполнением каждой итерации. Синтаксис этого оператора показан ниже:
+
+```cs
+for (инициализатор; условие; итератор)
+  оператор (операторы)
+```
+
+Здесь:
+
+**инициализатор** это выражение, вычисляемое перед первым выполнением тела цикла (обычно инициализация локальной переменной в качестве счетчика цикла). Инициализация, как правило, представлена оператором присваивания, задающим первоначальное значение переменной, которая выполняет роль счетчика и управляет циклом;
+
+**условие** это выражение, проверяемое перед каждой новой итерацией цикла (должно возвращать true, чтобы была выполнена следующая итерация);
+
+**итератор** - выражение, вычисляемое после каждой итерации (обычно приращение значения счетчика цикла).
+
+Обратите внимание на то, что эти три основные части оператора цикла **for** должны быть разделены точкой с запятой. Выполнение цикла **for** будет продолжаться до тех пор, пока проверка условия дает истинный результат. Как только эта проверка даст ложный результат, цикл завершится, а выполнение программы будет продолжено с оператора, следующего после цикла **for**.
+
+Стоит отметить, что цикл **for** отлично подходит для повторного выполнения оператора или блока операторов заранее известное количество раз. Давайте рассмотрим практическое применение цикла **for** на следующем примере:
+
+```cs
+// Данный метод выводит таблицу умножения
+// размерностью b x b
+static void tab(byte b)
+{
+    Console.WriteLine("Таблица умножения {0} x {0}\n", b);
+    // Этот цикл проходит по строкам
+    for (int i = 1; i <= b; i++)
+    {
+        // Этот цикл проходит по столбцам
+        for (int j = 1; j <= b; j++)
+            Console.Write("{0}\t", j * i);
+        Console.WriteLine();
+    }
+    Console.WriteLine();
+}
+
+tab(8);
+
+// Давайте разберем нестандартные возможности цикла for
+// ************************************************* //
+
+// Применение нескольких переменных управления циклом
+for (byte i = 0, j = 20; i <= j; i += 5, j -= 5)
+    Console.WriteLine("i = {0}, j = {1}",i,j);
+Console.WriteLine();
+
+// Использование условного выражения в цикле
+bool b = false;
+for (byte i = 1, j = 100; !b; i++, j--)
+    if (i < Math.Sqrt(j))
+        Console.WriteLine("Число {0} меньше квадратного корня из {1}", i, j);
+    else b = true;
+
+// Отсутствие части цикла
+int k = 0;
+for (; k < 10; )
+{
+    k++;
+    Console.Write(k);
+}
+Console.WriteLine("\n");
+
+// Цикл без тела
+int sum = 0;
+for (int i = 1; i <= 10; sum += ++i);
+
+Console.WriteLine("Значение суммы: {0}",sum);
+
+Console.ReadLine();
+```
+
+![](../img/03015.png)
+
+### Цикл while
+
+Подобно **for**, **while** также является циклом с предварительной проверкой. Синтаксис его аналогичен, но циклы **while** включают только одно выражение:
+
+```cs
+while(условие)
+    оператор (операторы);
+```
+
+где **оператор** — это единственный оператор или же блок операторов, а **условие** означает конкретное условие управления циклом и может быть любым логическим выражением. В этом цикле оператор выполняется до тех пор, пока условие истинно. Как только условие становится ложным, управление программой передается строке кода, следующей непосредственно после цикла.
+
+Как и в цикле **for**, в цикле **while** проверяется условное выражение, указываемое в самом начале цикла. Это означает, что код в теле цикла может вообще не выполняться, а также избавляет от необходимости выполнять отдельную проверку перед самим циклом.
+
+Пример:
+
+```cs
+// Пример возведения числа в несколько степеней
+byte l = 2, i = 0;
+int result = 1;
+
+while (i < 10)
+{
+    i++;
+    result *= l;
+    Console.WriteLine("{0} в степени {1} равно {2}",l,i,result);
+}
+
+Console.ReadLine();
+```
+
+![](../img/03016.png)
+
+### Цикл `do ... while`
+
+Цикл `do...while` в **C#** — это версия **while** с постпроверкой условия. Это значит, что условие цикла проверяется после выполнения тела цикла. Следовательно, циклы `do...while` удобны в тех ситуациях, когда блок операторов должен быть выполнен как минимум однажды. Ниже приведена общая форма оператора цикла `do...while`:
+
+```cs
+do {
+    операторы;
+} while (условие);
+```
+
+При наличии лишь одного **оператора** фигурные скобки в данной форме записи необязательны. Тем не менее они зачастую используются для того, чтобы сделать конструкцию `do...while` более удобочитаемой и не путать ее с конструкцией цикла `while`. Цикл `do...while` выполняется до тех пор, пока условное выражение истинно. В качестве примера использования цикла `do...while` можно привести следующую программу, расчитывающую факториал числа:
+
+```cs
+try
+{
+    // Вычисляем факториал числа
+    int i, result = 1, num = 1;
+
+    Console.WriteLine("Введите число:");
+    i = int.Parse(Console.ReadLine());
+
+    Console.Write("\n\nФакториал {0} = ", i);
+    do
+    {
+        result *= num;
+        num++;
+    } while (num <= i);
+
+    Console.Write(result);
+}
+catch (FormatException ex)
+{
+    Console.WriteLine("Вы ввели не число. {0}",ex.Message);
+}
+finally
+{
+    Console.ReadLine();
+}
+```
+
+<!-- TODO перенести к массивам -->
+
+### Цикл foreach
+
+Цикл **foreach** служит для циклического обращения к элементам **коллекции**, представляющей собой группу объектов. В **C#** определено несколько видов коллекций, каждая из которых является **массивом**. Ниже приведена общая форма оператора цикла foreach:
+
+```cs
+foreach (тип имя_переменной_цикла in коллекция) 
+    оператор;
+```
+
+Здесь **тип имя_переменной_цикла** обозначает **тип** и **имя переменной** управления циклом, которая получает значение следующего элемента коллекции на каждом шаге выполнения цикла **foreach**. А **коллекция** обозначает циклически опрашиваемую коллекцию, которая здесь и далее представляет собой массив. Следовательно, тип переменной цикла должен соответствовать типу элемента массива. Кроме того, тип может обозначаться ключевым словом **var**. В этом случае компилятор определяет тип переменной цикла, исходя из типа элемента массива. Это может оказаться полезным для работы с определенного рода запросами. Но, как правило, тип указывается явным образом.
+
+Оператор цикла **foreach** действует следующим образом. Когда цикл начинается, первый элемент массива выбирается и присваивается переменной цикла. На каждом последующем шаге итерации выбирается следующий элемент массива, который сохраняется в переменной цикла. Цикл завершается, когда все элементы массива окажутся выбранными.
+
+Цикл **foreach** позволяет проходить по каждому элементу коллекции (объект, представляющий список других объектов). Формально для того, чтобы нечто можно было рассматривать как коллекцию, это нечто должно поддерживать интерфейс **IEnumerable**. Примерами коллекций могут служить **массивы**, классы коллекций из пространства имен **System.Collection**, а также пользовательские классы коллекций.
+
+Пример использования цикла **foreach**:
+
+```cs
+// Объявляем два массива
+int[] myArr = new int[5];
+int[,] myTwoArr = new int[5, 6];
+int sum = 0;
+
+Random ran = new Random();
+
+// Инициализируем массивы
+for (int i = 1; i <= 5; i++)
+{
+    myArr[i-1] = ran.Next(1, 20);
+    for (int j = 1; j <= 6; j++)
+        myTwoArr[i - 1, j - 1] = ran.Next(1, 30);
+}
+
+// Вычисляем квадрат каждого элемента одномерного массива
+foreach (int fVar in myArr)
+    Console.WriteLine("{0} в квадрате равно {1}",fVar,fVar*fVar);
+
+Console.WriteLine();
+// Вычислим сумму элементов многомерного массива
+foreach (int fTwoVar in myTwoArr)
+    sum += fTwoVar;
+
+Console.WriteLine("Сумма элементов многомерного массива: {0}",sum);
+
+Console.ReadLine();
+```
+
+## Операторы перехода
+
+Язык **C#** предлагает несколько операторов, позволяющих немедленно перейти на другую строку программы. Давайте их рассмотрим.
+
+### Оператор goto
+
+Имеющийся в **C#** оператор **goto** представляет собой оператор безусловного перехода. Когда в программе встречается оператор **goto**, ее выполнение переходит непосредственно к тому месту, на которое указывает этот оператор. Он уже давно "вышел из употребления" в программировании, поскольку способствует созданию "макаронного" кода. Хотя в некоторых случаях он оказывается удобным и дает определенные преимущества, если используется благоразумно. Главный недостаток оператора **goto** с точки зрения программирования заключается в том, что он вносит в программу беспорядок и делает ее практически неудобочитаемой. Но иногда применение оператора **goto** может, скорее, прояснить, чем запутать ход выполнения программы.
+
+Для выполнения оператора **goto** требуется метка — действительный в **C#** идентификатор с двоеточием. Метка должна находиться в том же методе, где и оператор **goto**, а также в пределах той же самой области действия.
+
+Пример использования оператора **goto**:
+
+```cs
+    // Обычный цикл for выводящий числа от 1 до 5
+    Console.WriteLine("Обычный цикл for:");
+    for (int i = 1; i <= 5; i++)
+        Console.Write("\t{0}",i);
+
+    // Реализуем то же самое с помощью оператора goto
+    Console.WriteLine("\n\nА теперь используем goto:");
+    int j = 1;
+link1:     
+    Console.Write("\t{0}",j);
+    j++;
+    if (j <= 5) goto link1; 
+
+
+    Console.ReadLine();
+```
+
+Репутация оператора **goto** такова, что в большинстве случаев его применение категорически осуждается. Вообще говоря, он, конечно, не вписывается в рамки хорошей практики объектно-ориентированного программирования.
+
+### Оператор break
+
+С помощью оператора **break** можно организовать немедленный выход из цикла в обход любого кода, оставшегося в теле цикла, а также минуя проверку условия цикла. Когда в теле цикла встречается оператор **break**, цикл завершается, а выполнение программы возобновляется с оператора, следующего после этого цикла. Оператор **break** можно применять в любом цикле, предусмотренном в **C#**.
+
+```cs
+// В данном цикле выведутся числа от 1 до 5 вместо 100
+for (int i = 1; i < 100; i++)
+    if (i <= 5)
+        Console.WriteLine(i);
+    else 
+        break;
+
+Console.ReadLine();
+```
+
+Обратите внимание если оператор **break** применяется в целом ряде вложенных циклов, то он прерывает выполнение только самого внутреннего цикла.
+
+В отношении оператора **break** необходимо также иметь в виду следующее. Во-первых, в теле цикла может присутствовать несколько операторов **break**, но применять их следует очень аккуратно, поскольку чрезмерное количество операторов **break** обычно приводит к нарушению нормальной структуры кода. И во-вторых, оператор **break**, выполняющий выход из оператора **switch**, оказывает воздействие только на этот оператор, но не на объемлющие его циклы.
+
+### Оператор continue
+
+С помощью оператора **continue** можно организовать преждевременное завершение шага итерации цикла в обход обычной структуры управления циклом. Оператор **continue** осуществляет принудительный переход к следующему шагу цикла, пропуская любой код, оставшийся невыполненным. Таким образом, оператор **continue** служит своего рода дополнением оператора **break**.
+
+В циклах `while` и `do...while` оператор **continue** вызывает передачу управления непосредственно условному выражению, после чего продолжается процесс выполнения цикла. А в цикле **for** сначала вычисляется итерационное выражение, затем условное выражение, после чего цикл продолжается:
+
+```cs
+// Выводим числа кратные 5
+for (byte i = 1; i <= 100; i++)
+{
+    if (i % 5 != 0) continue;
+    Console.Write("\t{0}", i);
+}
+
+Console.ReadLine();
+```
+
+Оператор **continue** редко находит удачное применение, в частности, потому, что в **C#** предоставляется богатый набор операторов цикла, удовлетворяющих большую часть прикладных потребностей. Но в тех особых случаях, когда требуется преждевременное прерывание шага итерации цикла, оператор **continue** предоставляет структурированный способ осуществления такого прерывания.
+
+### Оператор return
+
+Оператор **return** организует возврат из метода. Его можно также использовать для возврата значения. Имеются две формы оператора **return**: одна — для методов типа **void**, т.е. тех методов, которые не возвращают значения, а другая — для методов, возвращающих конкретные значения.
+
+Для немедленного завершения метода типа **void** достаточно воспользоваться следующей формой оператора **return**:
+
+```cs
+return;
+```
+
+Когда выполняется этот оператор, управление возвращается вызывающей части программы, а оставшийся в методе код пропускается.
+
+Для возврата значения из метода в вызывающую часть программы служит следующая форма оператора **return**:
+
+```cs
+return значение;
+```
+
+Давайте рассмотрим применение оператора **return** на конкретном примере:
+
+```cs
+int result = Sum(230);
+Console.WriteLine("Сумма четных чисел от 1 до 230 равна: " + result);
+
+Console.ReadLine();
+
+// Метод, возращающий сумму всех четных чисел
+// от 1 до s
+static int Sum(int s)
+{
+    int mySum = 0;
+    for (int i = 1; i <= s; i++)
+        if (i % 2 == 0)
+            mySum += i;
+    return mySum;
+}
+```
+
+---
+
+## Контрольные вопросы
+
+1. Операторы отношения
+1. Логические операторы (обычные и укороченные)
+1. Условные операторы
+1. Тернарный оператор
+1. Циклы
+1. Оператор break
+1. Оператор continue
+1. Оператор return
+
+---
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Основные элементы языка.](./t3l1.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Массивы как структурированный тип данных.](./t3l1_3.md)
+

+ 711 - 0
articles/t3l1_3.md

@@ -0,0 +1,711 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Операторы и операции языка.](./t3l1_2.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Строки.](./4_prog_string.md)
+
+<!-- https://professorweb.ru/my/csharp/charp_theory/level4/4_1.php -->
+
+# Основы языка `C#`
+
+**Содержание:**
+
+* [Массивы](#массивы)
+* [Многомерные массивы](#многомерные-массивы)
+* [Класс Array](#класс-array)
+* [Массивы в качестве параметров](#массивы-в-качестве-параметров)
+* [Коллекции](#коллекции)
+    * [ArrayList](#arraylist)
+    * [List](#list)
+    * [Dictionary](#dictionary)
+
+## Массивы
+
+**Массив представляет собой совокупность переменных _одного типа_** с общим для обращения к ним именем. В **C#** массивы могут быть как одномерными, так и многомерными. Массивы служат самым разным целям, поскольку они предоставляют удобные средства для объединения связанных вместе переменных.
+
+Массивами в **C#** можно пользоваться практически так же, как и в других языках программирования. Тем не менее у них имеется одна особенность: они реализованы в виде объектов.
+
+Для тoго чтобы воспользоваться массивом в программе, требуется двухэтапная процедура, поскольку в **C#** массивы реализованы в виде объектов. Во-первых, необходимо объявить переменную, которая может обращаться к массиву. Например, команда `int[] arrayOfInteger` объявляет переменную с именем *ArrayOfInteger* с типом **int[]** - одномерный массив целых чисел. И во-вторых, нужно создать экземпляр массива, используя оператор **new**.
+
+```cs
+// Объявляем массив
+int[] myArr = new int[5];
+
+// Инициализируем каждый элемент массива вручную
+myArr[0] = 100;
+myArr[1] = 23;
+myArr[2] = 25;
+myArr[3] = 31;
+myArr[4] = 1;
+
+foreach (int i in myArr)
+    Console.WriteLine(i);
+
+Console.ReadLine();
+```
+
+Следует иметь в виду, что если массив только объявляется, но явно не инициализируется, каждый его элемент будет установлен в значение, принятое по умолчанию для соответствующего типа данных (например, элементы массива типа **bool** будут устанавливаться в `false`, а элементы массива типа **int** — в `0`).
+
+### Инициализация массива
+
+Помимо заполнения массива элемент за элементом (как показано в предыдущем примере), можно также заполнять его с использованием специального синтаксиса инициализации объектов. Для этого необходимо перечислить включаемые в массив элементы в фигурных скобках `{ }`. Такой синтаксис удобен при создании массива известного размера, когда нужно быстро задать его начальные значения:
+
+```cs
+// Синтаксис инициализации массива с использованием
+// ключевого слова new
+int[] myArr = new int[] {10,20,30,40,50};
+
+// Синтаксис инициализации массива без использования
+// ключевого слова new
+string[] info = { "Фамилия", "Имя", "Отчество" };
+
+// Используем ключевое слово new и желаемый размер
+char[] symbol = new char[4] { 'X','Y','Z','M' };
+```
+
+Обратите внимание, что в случае применения синтаксиса с фигурными скобками размер массива указывать не требуется (как видно на примере создания переменной *myArr*), поскольку этот размер автоматически вычисляется на основе количества элементов внутри фигурных скобок. Кроме того, применять ключевое слово **new** не обязательно (как при создании массива *info*).
+
+### Неявно типизированные массивы
+
+Ключевое слово **var** позволяет определить переменную так, чтобы лежащий в ее основе тип выводился компилятором. Аналогичным образом можно также определять неявно типизированные локальные массивы. С использованием такого подхода можно определить новую переменную массива без указания типа элементов, содержащихся в массиве. Давайте рассмотрим пример:
+
+```cs
+var arr1 = new[] { 1, 2, 3 };
+
+Console.WriteLine(
+    "Тип массива arr1 - {0}",
+    arr1.GetType());
+
+var arr2 = new[] { "One", "Two", "Three" };
+
+Console.WriteLine(
+    "Тип массива arr2 - {0}",
+    arr2.GetType());
+
+Console.ReadLine();
+```
+
+Разумеется, как и при создании массива с использованием явного синтаксиса **C#**, элементы, указываемые в списке инициализации массива, должны обязательно иметь один и тот же базовый тип (т.е. должны все быть **int**, **string** и т.п.).
+
+### Определение массива объектов
+
+В большинстве случаев при определении массива тип элемента, содержащегося в массиве, указывается явно. Хотя на первый взгляд это выглядит довольно понятно, существует одна важная особенность. В основе каждого типа в системе типов **.NET** (в том числе фундаментальных типов данных) в конечном итоге лежит базовый класс **System.Object**. В результате получается, что в случае определения массива объектов находящиеся внутри него элементы могут представлять собой что угодно:
+
+```cs
+// Объявляем и инициализируем массив объектов
+object[] arrByObject = { true, 10, "Привет", 13.7m };
+
+// Выведем в консоль тип каждого члена массива
+foreach (object me in arrByObject)
+    Console.WriteLine(
+        "Тип {0} - {1}",
+        me, me.GetType());
+
+Console.ReadLine();
+```
+
+![](../img/04001.png)
+
+### Свойство Length
+
+Реализация в **C#** массивов в виде объектов дает целый ряд преимуществ. Одно из них заключается в том, что с каждым массивом связано свойство **Length**, содержащее число элементов, из которых может состоять массив. Следовательно, у каждого массива имеется специальное свойство, позволяющее определить его длину.
+
+Когда запрашивается длина многомерного массива, то возвращается общее число элементов, из которых может состоять массив. Благодаря наличию у массивов свойства **Length** операции с массивами во многих алгоритмах становятся более простыми, а значит, и более надежными. Давайте рассмотрим пример использования свойства **Length**:
+
+```cs
+int[] myArr = { 1, 2, 3, 4 };
+
+for (int i = 0; i < myArr.Length; i++)
+    Console.WriteLine(myArr[i]);
+
+Console.ReadLine();
+```
+
+## Многомерные массивы
+
+**Многомерным** называется такой массив, который содержит два или более измерения, причем доступ к каждому элементу такого массива осуществляется с помощью определенной комбинации двух или более индексов. Многомерный массив индексируется двумя и более целыми числами.
+
+### Двумерные массивы
+
+Простейшей формой многомерного массива является двумерный массив. Местоположение любого элемента в двумерном массиве обозначается двумя индексами. Такой массив можно представить в виде таблицы, на строки которой указывает один индекс, а на столбцы — другой. Пример объявления и инициализации двумерного массива показан ниже:
+
+```cs
+// Объявляем двумерный массив
+int[,] myArr = new int[4, 5];
+
+Random ran = new Random();
+
+// Инициализируем данный массив
+for (int i = 0; i < 4; i++)
+{
+    for (int j = 0; j < 5; j++)
+    {
+       myArr[i, j] = ran.Next(1, 15);
+       Console.Write("{0}\t", myArr[i, j]);
+    }
+    Console.WriteLine();
+}
+```
+
+![](../img/04002.png)
+
+Обратите особое внимание на способ объявления двумерного массива. Схематическое представление массива myArr показано ниже:
+
+![](../img/04003.png)
+
+Если вам приходилось раньше программировать на **С**, **С++** или **Java**, то будьте особенно внимательны, объявляя или организуя доступ к многомерным массивам в **C#**. В этих языках программирования размеры массива и индексы указываются в отдельных квадратных скобках, тогда как в **C#** они разделяются запятой.
+
+### Массивы трех и более измерений
+
+В C# допускаются массивы трех и более измерений. Ниже приведена общая форма объявления многомерного массива:
+
+```
+тип[,...,] имя_массива = new тип[размер1, размер2, ... размеры];
+```
+
+Ниже приведен пример программы, использующей трехмерный массив:
+
+```cs
+int[,,] myArr = new int[5,5,5];
+
+for (int i = 0; i < 5; i++)
+   for (int j = 0; j < 5; j++)
+      for (int k = 0; k < 5; k++)
+         myArr[i, j, k] = i + j + k;
+```
+
+### Инициализация многомерных массивов
+
+Для инициализации многомерного массива достаточно заключить в фигурные скобки список инициализаторов каждого его размера:
+
+```
+тип[,] имя_массива = {
+    {val, val, val, ..., val},
+    {val, val, val, ..., val},
+    {val, val, val, ..., val}
+};
+```
+
+где *val* обозначает инициализирующее значение, а каждый внутренний блок — отдельный ряд. Первое значение в каждом ряду сохраняется на первой позиции в массиве, второе значение — на второй позиции и т.д. Обратите внимание на то, что блоки инициализаторов разделяются запятыми, а после завершающей эти блоки закрывающей фигурной скобки ставится точка с запятой.
+
+Ниже в качестве примера приведена общая форма инициализации двумерного массива:
+
+```cs
+int[,] MyArr = {
+    {1,10},
+    {2,20},
+    {3,30},
+    {4,40}
+};
+```
+
+## Ступенчатые (зубчатые) массивы
+
+Двумерный массив можно представить в виде таблицы, в которой длина каждой строки остается неизменной по всему массиву. Но в C# можно также создавать специальный тип двумерного массива, называемый ступенчатым массивом. Ступенчатый массив представляет собой массив массивов, в котором длина каждого массива может быть разной. Следовательно, ступенчатый массив может быть использован для составления таблицы из строк разной длины.
+
+Ступенчатые массивы объявляются с помощью ряда квадратных скобок, в которых указывается их размерность. Например, для объявления двумерного ступенчатого массива служит следующая общая форма:
+
+```
+тип [][] имя массива = new тип[размер] [];
+```
+
+где размер обозначает число строк в массиве. Память для самих строк распределяется индивидуально, и поэтому длина строк может быть разной.
+
+Давайте рассмотрим пример использования ступенчатого массива:
+
+```cs
+int i = 0;
+// Объявляем ступенчатый массив
+int[][] myArr = new int[4][];
+myArr[0] = new int[4];
+myArr[1] = new int[6];
+myArr[2] = new int[3];
+myArr[3] = new int[4];
+
+// Инициализируем ступенчатый массив
+for (; i < 4; i++)
+{
+    myArr[0][i] = i;
+    Console.Write("{0}\t",myArr[0][i]);
+}
+
+Console.WriteLine();
+for (i = 0; i < 6; i++)
+{
+    myArr[1][i] = i;
+    Console.Write("{0}\t", myArr[1][i]);
+}
+
+Console.WriteLine();
+for (i = 0; i < 3; i++)
+{
+    myArr[2][i] = i;
+    Console.Write("{0}\t", myArr[2][i]);
+}
+
+Console.WriteLine();
+for (i = 0; i < 4; i++)
+{
+    myArr[3][i] = i;
+    Console.Write("{0}\t", myArr[3][i]);
+}
+
+Console.ReadLine();
+```
+
+Созданный массив в данном примере можно структурно представить в виде:
+
+![](../img/04004.png)
+
+Теперь нетрудно понять, почему такие массивы называются ступенчатыми! После создания ступенчатого массива доступ к его элементам осуществляется по индексу, указываемому в отдельных квадратных скобках.
+
+Ступенчатые массивы находят полезное применение не во всех, а лишь в некоторых случаях. Так, если требуется очень длинный двумерный массив, который заполняется не полностью, т.е. такой массив, в котором используются не все, а лишь отдельные его элементы, то для этой цели идеально подходит ступенчатый массив.
+
+### Применение свойства *Length* при обращении со ступенчатыми массивами
+
+Особый случай представляет применение свойства **Length** при обращении со ступенчатыми массивами. В этом случае с помощью данного свойства можно получить длину каждого массива, составляющего ступенчатый массив. Давайте рассмотрим следующий пример:
+
+```cs
+int[][] myArr = new int[3][];
+myArr[0] = new int[4];
+myArr[1] = new int[10];
+myArr[2] = new int[1];
+
+Console.WriteLine("Общая длина всего массива: " + myArr.Length);
+Console.WriteLine("\nДлина первой цепи: " + myArr[0].Length);
+Console.WriteLine("\nДлина второй цепи: " + myArr[1].Length);
+Console.WriteLine("\nДлина третьей цепи: " + myArr[2].Length);
+
+Console.ReadLine();
+```
+
+![](../img/04005.png)
+
+
+Обратите особое внимание на то, как свойство *Length* используется в ступенчатом массиве *myArr*. Напомним, что двумерный ступенчатый массив представляет собой массив массивов. Следовательно, когда используется выражение `myArr.Length` то в нем определяется число массивов, хранящихся в массиве _myArr_ (в данном случае — 3 массива). А для получения длины любого отдельного массива, составляющего ступенчатый массив, служит выражение `myArr[0].Length`.
+
+## Класс Array
+
+Каждый создаваемый массив получает большую часть функциональности от класса **System.Array**. Общие члены этого класса позволяют работать с массивом с использованием полноценной объектной модели. Таким образом, методы и свойства, определенные в классе **Array**, можно использовать с любым массивом **C#**. Давайте разберем некоторые полезные особенности класса **Array**.
+
+### Создание массивов
+
+Класс **Array** является абстрактным, поэтому создать массив с использованием какого-либо конструктора нельзя. Однако вместо применения синтаксиса **C#** для создания экземпляров массивов также возможно создавать их с помощью статического метода *CreateInstance()*. Это исключительно удобно, когда заранее неизвестен тип элементов массива, поскольку тип можно передать методу *CreateInstance()* в параметре как объект **Type**:
+
+```cs
+// Создаем массив типа string, длиной 5
+Array myArr = Array.CreateInstance(typeof(string),5);
+
+// Инициализируем первые два поля массива
+myArr.SetValue("Name",0);
+myArr.SetValue("Age",1);
+
+// Считываем данные из массива
+string s = (string)myArr.GetValue(1);
+
+Console.ReadLine();
+```
+
+Обратите внимание, что для установки значений в массив служит метод *SetValue*, а для их чтения — метод *GetValue*.
+
+### Копирование массивов
+
+Поскольку массивы — это ссылочные типы, присваивание переменной типа массива другой переменной создает две переменных, ссылающихся на один и тот же массив. Для копирования массивов предусмотрена реализация массивами интерфейса **ICloneable**. Метод *Clone*, определенный в этом интерфейсе, создает неглубокую копию массива. Если элементы массива относятся к типу значений, то все они копируются, если массив содержит элементы ссылочных типов, то сами эти элементы не копируются, а копируются лишь ссылки на них.
+
+Вместо метода *Clone* можно также применять метод *Array.Сору*, тоже создающий поверхностную копию. Но между *Clone* и *Сору* есть одно важное отличие: *Clone()* создает новый массив, а *Сору* требует наличия существующего массива той же размерности с достаточным количеством элементов.
+
+```cs
+string[] arr2 = new string[5];
+// Создаем массив типа string, длиной 5
+Array myArr = Array.CreateInstance(typeof(string),5);
+
+// Инициализируем первые три поля массива
+myArr.SetValue("Name",0);
+myArr.SetValue("Age",1);
+myArr.SetValue("Adress",2);
+
+// Копируем массив с помощью метода Clone()
+string[] arr1 = (string[])myArr.Clone();
+
+foreach (string s in arr1)
+    Console.Write("\t"+s);
+
+// Копируем с помощью метода Copy()
+Console.WriteLine();
+Array.Copy(myArr, arr2, myArr.Length);
+foreach (string s in arr2)
+    Console.Write("\t"+s);
+
+Console.ReadLine();
+```
+
+### Сортировка и поиск
+
+В классе **Array** реализован алгоритм быстрой сортировки (**Quicksort**) элементов массива. Метод *Sort* требует от элементов реализации интерфейса **IComparable**. Простые типы, такие как **System.String** и **System.Int32**, реализуют **IComparable**, так что можно сортировать элементы, относящиеся к этим типам.
+
+C помощью разных вариантов метода *Sort* можно отсортировать массив полностью или в заданных пределах либо отсортировать два массива, содержащих соответствующие пары "ключ-значение". После сортировки в массиве можно осуществить эффективный поиск, используя разные варианты метода *BinarySearch*.
+
+```cs
+int[] myArr = { 4, 5, -183, 12, 34, 0, 2 ,-13 };
+
+Console.WriteLine(
+    "Исходный массив чисел: ");
+
+foreach (int x in myArr)
+    Console.Write("\t{0}",x);
+
+// Реализуем сортировку массива
+Console.WriteLine(
+    "\n\nОтсортированный массив:");
+
+Array.Sort(myArr);
+
+foreach (int x in myArr)
+    Console.Write("\t{0}",x);
+
+// Организуем поиск числа 12
+Console.WriteLine("\n\nПоиск числа:");
+
+int search = Array.BinarySearch(myArr, 12);
+
+Console.WriteLine(
+    "Число 12 находится на {0} позиции",
+    search+1);
+
+Console.ReadLine();
+```
+
+![](../img/04006.png)
+
+## Массивы в качестве параметров
+
+Массивы могут передаваться в методы в качестве параметров, а также возвращаться из методов. Для возврата массива достаточно объявить массив как тип возврата. Наглядно это показано в следующем примере:
+
+```cs
+foreach (string s in myReturn())
+    Console.WriteLine(s);
+
+int[] mySortArray = {4,1,2,5,3};
+
+for (int i = 0; i <= 4; i++)
+    mySortArray[i] = mySort(mySortArray, i);
+
+Console.WriteLine(
+    "\nОтсортированный массив:\n");
+
+foreach (int i in mySortArray)
+    Console.Write("\t"+i);
+
+Console.ReadLine();
+
+// Данный метод возвращает массив Info
+static Array myReturn()
+{
+    string[] Info = {"Name","Family","Adress" };
+    return Info;
+}
+
+// Передача массива в метод в качестве параметра
+// Данный метод возвращает значение одного из индексов
+// отсортированного массива
+static int mySort(Array sortArray, int i)
+{
+    Array.Sort(sortArray);
+    return (int)sortArray.GetValue(i);
+}
+```
+
+Результат работы данной программы:
+
+![](../img/04007.png)
+
+### Структура ArraySegment`<T>`
+
+Структура **ArraySegment`<T>`** представляет сегмент массива. Эта структура может применяться, когда нужно вернуть или передать методу части массива. Вместо передачи в метод массива, смещения и счетчика в отдельных параметрах, можно передать единственный параметр **ArraySegment`<T>`**. В этой структуре информация о сегменте (смещение и счетчик) заключена непосредственно в ее членах:
+
+```cs
+int[] arr1 = { 1, 2, 3, 4, 5, 6 };
+int[] arr2 = { 7, 8, 9, 10 };
+
+var mySegmentsArray = new ArraySegment<int>[3]
+{
+    // Инициализируем сегменты массивов
+    new ArraySegment<int>(arr1,0,2),
+    new ArraySegment<int>(arr2,0,1),
+    new ArraySegment<int>(arr1,1,2)
+};
+
+Console.WriteLine("Сумма выбранных значений равна:\n"+SumArraySegments(mySegmentsArray));
+Console.ReadLine();
+
+// Метод, вычисляющий сумму выбранных сегментов
+static int SumArraySegments(ArraySegment<int>[] value)
+{
+    int sum = 0;
+    foreach (var s in value)
+        for (int i = s.Offset; i < s.Offset + s.Count; i++)
+            sum += s.Array[i];
+    return sum;
+}
+```
+
+Важно отметить, что сегменты массива не копируют элементы исходного массива. Вместо этого через **ArraySegment`<T>`** можно получить доступ к исходному массиву. Если изменяются элементы сегмента, то эти изменения будут видны в исходном массиве.
+
+<!-- TODO дописать про кортежи (в отдельной лекции про типы данных) -->
+
+## Коллекции 
+
+<!-- https://metanit.com/sharp/tutorial/4.3.php -->
+
+Работать с массивами не всегда удобно. Например, массив хранит фиксированное количество объектов, однако что делать если мы заранее не знаем, сколько нам потребуется объектов? В этом случае намного удобнее применять **коллекции**. Еще один плюс коллекций состоит в том, что некоторые из них реализует стандартные структуры данных, например, **стек**, **очередь**, **словарь**, которые могут пригодиться для решения различных специальных задач.
+
+Большая часть классов коллекций содержится в пространствах имен **System.Collections** (простые необобщенные классы коллекций), **System.Collections.Generic** (обобщенные или типизированные классы коллекций) и **System.Collections.Specialized** (специальные классы коллекций). Также для обеспечения параллельного выполнения задач и многопоточного доступа применяются классы коллекций из пространства имен **System.Collections.Concurrent**.
+
+### ArrayList
+
+Итак, класс **ArrayList** представляет коллекцию объектов. И если надо сохранить вместе разнотипные объекты - строки, числа и т.д., то данный класс как раз для этого подходит.
+
+**Основные методы класса:**
+
+* `int Add(object value)`: добавляет в список объект *value*
+
+* `void AddRange(ICollection col)`: добавляет в список объекты коллекции *col*, которая представляет интерфейс ICollection - интерфейс, реализуемый коллекциями.
+
+* `void Clear()`: удаляет из списка все элементы
+
+* `bool Contains(object value)`: проверяет, содержится ли в списке объект *value*. Если содержится, возвращает true, иначе возвращает false
+
+* `void CopyTo(Array array)`: копирует текущий список в массив array.
+
+* `ArrayList GetRange(int index, int count)`: возвращает новый список *ArrayList*, который содержит *count* элементов текущего списка, начиная с индекса *index*
+
+* `int IndexOf(object value)`: возвращает индекс элемента *value*
+
+* `void Insert(int index, object value)`: добавляет в список по индексу *index* объект *value*
+
+* `void InsertRange(int index, ICollection col)`: добавляет в список начиная с индекса index коллекцию ICollection
+
+* `int LastIndexOf(object value)`: возвращает индекс последнего вхождения в списке объекта value
+
+* `void Remove(object value)`: удаляет из списка объект value
+
+* `void RemoveAt(int index)`: удаляет из списка элемент по индексу index
+
+* `void RemoveRange(int index, int count)`: удаляет из списка count элементов, начиная с индекса index
+
+* `void Reverse()`: переворачивает список
+
+* `void SetRange(int index, ICollection col)`: копирует в список элементы коллекции col, начиная с индекса index
+
+* `void Sort()`: сортирует коллекцию
+
+Кроме того, с помощью свойства *Count* можно получить количество элементов в списке.
+
+Посмотрим применение класса **ArrayList** на примере.
+
+```cs
+ArrayList list = new ArrayList();
+list.Add(2.3); // заносим в список объект типа double
+list.Add(55); // заносим в список объект типа int
+list.AddRange(new string[] { "Hello", "world" }); // заносим в список строковый массив
+
+// перебор значений
+foreach (object o in list)
+{
+    Console.WriteLine(o);
+}
+
+// удаляем первый элемент
+list.RemoveAt(0);
+// переворачиваем список
+list.Reverse();
+// получение элемента по индексу
+Console.WriteLine(list[0]);
+// перебор значений
+for (int i = 0; i < list.Count; i++)
+{
+    Console.WriteLine(list[i]);
+}
+
+Console.ReadLine();
+```
+
+Во-первых, так как класс **ArrayList** находится в пространстве имен **System.Collections**, то подключаем его (`using System.Collections;`).
+
+Вначале создаем объект коллекции через конструктор как объект любого другого класса: `ArrayList list = new ArrayList();`. При необходимости мы могли бы так же, как и с массивами, выполнить начальную инициализацию коллекции, например, `ArrayList list = new ArrayList(){1, 2, 5, "string", 7.7};`
+
+Далее последовательно добавляем разные значения. Данный класс коллекции, как и большинство других коллекций, имеет два способа добавления: одиночного объекта через метод *Add* и набора объектов, например, массива или другой коллекции через метод *AddRange*
+
+Через цикл *foreach* мы можем пройтись по всем объектам списка. И поскольку данная коллекция хранит разнородные объекты, а не только числа или строки, то в качестве типа перебираемых объектов выбран тип object: `foreach (object o in list)`
+
+Многие коллекции, в том числе и *ArrayList*, реализуют удаление с помощью методов *Remove/RemoveAt*. В данном случае мы удаляем первый элемент, передавая в метод RemoveAt индекс удаляемого элемента.
+
+В завершении мы опять же выводим элементы коллекции на экран только уже через цикл for. В данном случае с перебором коллекций дело обстоит также, как и с массивами. А число элементов коллекции мы можем получить через свойство Count
+
+С помощью индекса мы можем получить элемент коллекции так же, как и в массивах: `object firstObj = list[0];`
+
+### List
+
+Класс **List`<T>`** из пространства имен **System.Collections.Generic** представляет простейший список однотипных объектов (в угловых скобках как раз записывается тип объектов).
+
+Среди его методов можно выделить следующие:
+
+* `void Add(T item)`: добавление нового элемента в конец списка
+
+* `void AddRange(ICollection collection)`: добавление в список коллекции или массива
+
+* `int BinarySearch(T item)`: бинарный поиск элемента в списке. Если элемент найден, то метод возвращает индекс этого элемента в коллекции. При этом список ***должен быть отсортирован***.
+
+* `int IndexOf(T item)`: возвращает индекс первого вхождения элемента в списке
+
+* `void Insert(int index, T item)`: вставляет элемент item в списке на позицию index
+
+* `bool Remove(T item)`: удаляет элемент item из списка, и если удаление прошло успешно, то возвращает true
+
+* `void RemoveAt(int index)`: удаление элемента по указанному индексу index
+
+* `void Sort()`: сортировка списка
+
+Посмотрим реализацию списка на примере:
+
+```cs
+List<int> numbers = new List<int>() { 1, 2, 3, 45 };
+numbers.Add(6); // добавление элемента
+
+numbers.AddRange(new int[] { 7, 8, 9 });
+
+numbers.Insert(0, 666); // вставляем на первое место в списке число 666
+
+numbers.RemoveAt(1); //  удаляем второй элемент
+
+foreach (int i in numbers)
+{
+    Console.WriteLine(i);
+}
+
+List<Person> people = new List<Person>(3);
+people.Add(new Person() { Name = "Том" });
+people.Add(new Person() { Name = "Билл" });
+
+foreach (Person p in people)
+{
+    Console.WriteLine(p.Name);
+}
+
+Console.ReadLine();
+
+class Person
+{
+    public string Name { get; set; }
+}
+```
+
+Здесь у нас создаются два списка: один для объектов типа **int**, а другой - для объектов **Person**. В первом случае мы выполняем начальную инициализацию списка: `List<int> numbers = new List<int>() { 1, 2, 3, 45 };`
+
+Во втором случае мы используем другой конструктор, в который передаем начальную емкость списка: `List<Person> people = new List<Person>(3);`. Указание начальной емкости списка (capacity) позволяет в будущем увеличить производительность и уменьшить издержки на выделение памяти при добавлении элементов. Также начальную емкость можно установить с помощью свойства *Capacity*, которое имеется у класса **List**.
+
+### Dictionary
+
+Еще один распространенный тип коллекции представляют **словари**. Словарь хранит объекты, которые представляют пару ключ-значение. Каждый такой объект является объектом структуры **KeyValuePair`<TKey, TValue>`**. Благодаря свойствам *Key* и *Value*, которые есть у данной структуры, мы можем получить ключ и значение элемента в словаре.
+
+Рассмотрим на примере использование словарей:
+
+```cs
+Dictionary<int, string> countries = new Dictionary<int, string>(5);
+countries.Add(1, "Russia");
+countries.Add(3, "Great Britain");
+countries.Add(2, "USA");
+countries.Add(4, "France");
+countries.Add(5, "China");          
+ 
+foreach (KeyValuePair<int, string> keyValue in countries)
+{
+    Console.WriteLine(keyValue.Key + " - " + keyValue.Value);
+}
+ 
+// получение элемента по ключу
+string country = countries[4];
+// изменение объекта
+countries[4] = "Spain";
+// удаление по ключу
+countries.Remove(2);
+```
+
+Класс словарей также, как и другие коллекции, предоставляет методы *Add* и *Remove* для добавления и удаления элементов. Только в случае словарей в метод *Add* передаются два параметра: ключ и значение. А метод *Remove* удаляет не по индексу, а по ключу.
+
+Так как в нашем примере ключами является объекты типа int, а значениями - объекты типа string, то словарь в нашем случае будет хранить объекты **KeyValuePair`<int, string>`**. В цикле **foreach** мы их можем получить и извлечь из них ключ и значение.
+
+Кроме того, мы можем получить отдельно коллекции ключей и значений словаря:
+
+```cs
+Dictionary<char, Person> people = new Dictionary<char, Person>();
+people.Add('b', new Person() { Name = "Bill" });
+people.Add('t', new Person() { Name = "Tom" }); 
+people.Add('j', new Person() { Name = "John" });
+ 
+foreach (KeyValuePair<char, Person> keyValue in people)
+{
+    // keyValue.Value представляет класс Person
+    Console.WriteLine(keyValue.Key + " - " + keyValue.Value.Name); 
+}
+ 
+// перебор ключей
+foreach (char c in people.Keys)
+{
+    Console.WriteLine(c);
+}
+ 
+// перебор по значениям
+foreach (Person p in people.Values)
+{
+    Console.WriteLine(p.Name);
+}
+```
+
+Здесь в качестве ключей выступают объекты типа **char**, а значениями - объекты **Person**. Используя свойство *Keys*, мы можем получить ключи словаря, а свойство _Values_ соответственно хранит все значения в словаре.
+
+Для добавления необязательно применять метод `Add`, можно использовать сокращенный вариант:
+
+```cs
+Dictionary<char, Person> people = new Dictionary<char, Person>();
+people.Add('b', new Person() { Name = "Bill" });
+people['a'] = new Person() { Name = "Alice" };
+```
+
+Несмотря на то, что изначально в словаре нет ключа `a` и соответствующего ему элемента, то он все равно будет установлен. Если же он есть, то элемент по ключу `a` будет заменен на новый объект `new Person() { Name = "Alice" }`
+
+**Инициализация словарей**
+
+Если в **C#** 5.0 мы могли инициализировать словари следующим образом:
+
+```cs
+Dictionary<string, string> countries = new Dictionary<string, string>
+{
+    {"Франция", "Париж"},
+    {"Германия", "Берлин"},
+    {"Великобритания", "Лондон"}
+};
+ 
+foreach(var pair in countries)
+    Console.WriteLine("{0} - {1}", pair.Key, pair.Value);
+```
+
+То начиная с **C#** 6.0 доступен также еще один способ инициализации:
+
+```cs
+Dictionary<string, string> countries = new Dictionary<string, string>
+{
+    ["Франция"] = "Париж",
+    ["Германия"] = "Берлин",
+    ["Великобритания"] = "Лондон"
+}; 
+```
+
+---
+
+## Контрольные вопросы
+
+1. [Массивы](#массивы)
+1. [Коллекции](#коллекции)
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Операторы и операции языка.](./t3l1_2.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Строки.](./4_prog_string.md)

+ 701 - 0
articles/t5_delegate.md

@@ -0,0 +1,701 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Общие сведения о подпрограммах.](./t5_function.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [Исключения. Null.](./t5_exception.md)
+
+# Делегаты, события и лямбды.
+
+* [Делегаты](#делегаты)
+* [Применение делегатов](#применение-делегатов)
+* [Лямбда-выражения](#лямбда-выражения)
+
+## Делегаты
+
+Делегаты представляют такие объекты, которые указывают на методы. То есть делегаты - это указатели на методы и с помощью делегатов мы можем вызвать данные методы.
+
+### Определение делегатов
+
+Для объявления делегата используется ключевое слово **delegate**, после которого идет _возвращаемый тип_, _название_ и _параметры_. Например:
+
+```cs
+delegate void Message();
+```
+
+Делегат _Message_ в качестве возвращаемого типа имеет тип **void** (то есть ничего не возвращает) и не принимает никаких параметров. Это значит, что этот делегат может указывать на любой метод, который не принимает никаких параметров и ничего не возвращает.
+
+Рассмотрим примение этого делегата:
+
+```cs
+// 1. Объявляем делегат
+delegate void Message(); 
+ 
+// 2. Создаем переменную делегата 
+Message mes; 
+
+if (DateTime.Now.Hour < 12)
+{
+    // 3.1 Присваиваем этой переменной адрес метода
+    mes = GoodMorning; 
+}
+else
+{   
+    // 3.2 или дрогого
+    mes = GoodEvening;
+}
+
+// 4. Вызываем метод
+mes(); 
+
+Console.ReadKey();
+
+private static void GoodMorning()
+{
+    Console.WriteLine("Good Morning");
+}
+private static void GoodEvening()
+{
+    Console.WriteLine("Good Evening");
+}
+```
+
+Здесь сначала мы определяем делегат:
+
+```cs
+delegate void Message(); 
+```
+
+Для использования делегата объявляется переменная этого делегата:
+
+```cs
+Message mes; 
+```
+
+С помощью свойства _DateTime.Now.Hour_ получаем текущий час. И в зависимости от времени в делегат передается адрес определенного метода. Обратите внимание, что методы эти имеют то же возвращаемое значение и тот же набор параметров (в данном случае отсутствие параметров), что и делегат.
+
+```cs
+mes = GoodMorning;
+```
+
+Затем через делегат вызываем метод, на который ссылается данный делегат:
+
+```cs
+mes();
+```
+
+Вызов делегата производится подобно вызову метода.
+
+Посмотрим на примере другого делегата:
+
+```cs
+delegate int Operation(int x, int y);
+     
+// делегат указывает на метод Add
+Operation del = Add; 
+
+// фактически Add(4, 5)
+int result = del(4,5); 
+
+Console.WriteLine(result);
+
+// теперь делегат указывает на метод Multiply
+del = Multiply; 
+
+// фактически Multiply(4, 5)
+result = del(4, 5); 
+
+Console.WriteLine(result);
+
+Console.Read();
+
+private static int Add (int x, int y) => x+y;
+private static int Multiply (int x, int y) => x*y;
+```
+
+В данном случае делегат _Operation_ возвращает значение типа **int** и имеет два параметра типа **int**. Поэтому этому делегату соответствует любой метод, который возвращает значение типа **int** и принимает два параметра типа **int**. В данном случае это методы _Add_ и _Multiply_. То есть мы можем присвоить переменной делегата любой из этих методов и вызывать.
+
+Поскольку делегат принимает два параметра типа **int**, то при его вызове необходимо передать значения для этих параметров: `del(4, 5)`.
+
+Делегаты необязательно могут указывать только на методы, которые определены в том же классе, где определена переменная делегата. Это могут быть также методы из других классов и структур.
+
+```cs
+class Math
+{
+    public int Sum(int x, int y) => x + y;
+}
+
+delegate int Operation(int x, int y);
+
+Math math = new Math();
+Operation del = math.Sum;
+int result = del(4, 5);     // math.Sum(4, 5)
+Console.WriteLine(result);  // 9
+
+Console.Read();
+```
+
+### Присвоение ссылки на метод
+
+Выше переменной делегата напрямую присваивался метод. Есть еще один способ - создание объекта делегата с помощью конструктора, в который передается нужный метод:
+
+```cs
+delegate int Operation(int x, int y);
+ 
+Operation del = Add;
+Operation del2 = new Operation(Add);
+
+Console.Read();
+
+private static int Add(int x, int y) => x + y;
+```
+
+Оба способа равноценны.
+
+### Соответствие методов делегату
+
+Как было написано выше, методы соответствуют делегату, если они имеют один и тот же возвращаемый тип и один и тот же набор параметров. Но надо учитывать, что во внимание также принимаются модификаторы **ref** и **out**. Например, пусть у нас есть делегат:
+
+```cs
+delegate void SomeDel(int a, double b);
+```
+
+Этому делегату соответствует, например, следующий метод:
+
+```cs
+void SomeMethod1(int g, double n) { }
+```
+
+А следующие методы НЕ соответствуют:
+
+```cs
+int SomeMethod2(int g, double n)
+void SomeMethod3(double n, int g)
+void SomeMethod4(ref int g, double n)
+void SomeMethod5(out int g, double n)
+```
+
+Здесь метод _SomeMethod2_ имеет другой возвращаемый тип, отличный от типа делегата. _SomeMethod3_ имеет другой набор параметров. Параметры _SomeMethod4_ и _SomeMethod5_ также отличаются от параметров делегата, поскольку имеют модификаторы **ref** и **out**.
+
+### Добавление методов в делегат
+
+В примерах выше переменная делегата указывала на один метод. В реальности же делегат может указывать на множество методов, которые имеют ту же сигнатуру и возвращаемые тип. Все методы в делегате попадают в специальный список - список вызова или **invocation list**. И при вызове делегата все методы из этого списка последовательно вызываются. И мы можем добавлять в этот спиок не один, а несколько методов:
+
+```cs
+delegate void Message();
+ 
+Message mes = Hello;
+
+// теперь mes указывает на два метода
+mes += HowAreYou;
+
+// вызываются оба метода - Hello и HowAreYou
+mes();
+
+Console.Read();
+
+private static void Hello()
+{
+    Console.WriteLine("Hello");
+}
+private static void HowAreYou()
+{
+    Console.WriteLine("How are you?");
+}
+```
+
+В данном случае в список вызова делегата _mes_ добавляются два метода - _Hello_ и _HowAreYou_. И при вызове _mes_ вызываются сразу оба этих метода.
+
+Для добавления делегатов применяется операция `+=`. Однако стоит отметить, что в реальности будет происходить создание нового объекта делегата, который получит методы старой копии делегата и новый метод, и новый созданный объект делеагата будет присвоен переменной _mes_.
+
+При добавлении делегатов следует учитывать, что мы можем добавить ссылку на один и тот же метод несколько раз, и в списке вызова делегата тогда будет несколько ссылок на один и то же метод. Соответственно при вызове делегата добавленный метод будет вызываться столько раз, сколько он был добавлен:
+
+```cs
+Message mes = Hello;
+mes += HowAreYou;
+mes += Hello;
+mes += Hello;
+ 
+mes();
+```
+
+Консольный вывод:
+
+```
+Hello
+How are you?
+Hello
+Hello
+```
+
+Подобным образом мы можем удалять методы из делегата с помощью операции `-=`:
+
+```cs
+Message mes = Hello;
+mes += HowAreYou;
+
+// вызываются все методы из mes1
+mes();
+
+// удаляем метод HowAreYou
+mes -= HowAreYou;
+
+// вызывается метод Hello
+mes();
+    
+Console.Read();
+```
+
+При удалении методов из делегата фактически будет создаватья новый делегат, который в списке вызова методов будет содержать на один метод меньше.
+
+При удалении следует учитывать, что если делегат содержит несколько ссылок на один и тот же метод, то операция `-=` начинает поиск с конца списка вызова делегата и удаляет только первое найденное вхождение. Если подобного метода в списке вызова делегата нет, то операция `-=` не имеет никакого эффекта.
+
+### Объединение делегатов
+
+Делегаты можно объединять в другие делегаты. Например:
+
+```cs
+delegate void Message();
+ 
+Message mes1 = Hello;
+Message mes2 = HowAreYou;
+Message mes3 = mes1 + mes2; // объединяем делегаты
+
+mes3(); // вызываются все методы из mes1 и mes2
+    
+Console.Read();
+
+private static void Hello()
+{
+    Console.WriteLine("Hello");
+}
+private static void HowAreYou()
+{
+    Console.WriteLine("How are you?");
+}
+```
+
+В данном случае объект _mes3_ представляет объединение делегатов _mes1_ и _mes2_. Объединение делегатов значит, что в список вызова делегата _mes3_ попадут все методы из делегатов _mes1_ и _mes2_. И при вызове делегата _mes3_ все эти методы одновременно будут вызваны.
+
+### Вызов делегата
+
+В примерах выше делегат вызывался как обычный метод. Если делегат принимал параметры, то при ее вызове для параметров передавались необходимые значения:
+
+```cs
+delegate int Operation(int x, int y);
+delegate void Message();
+
+Message mes = Hello;
+mes();
+Operation op = Add;
+op(3, 4);
+Console.Read();
+
+private static void Hello() { Console.WriteLine("Hello"); }
+private static int Add(int x, int y) { return x + y; }
+```
+
+Другой способ вызова делегата представляет метод `Invoke()`:
+
+```cs
+delegate int Operation(int x, int y);
+delegate void Message();
+
+Message mes = Hello;
+mes.Invoke();
+Operation op = Add;
+op.Invoke(3, 4);
+Console.Read();
+
+private static void Hello() { Console.WriteLine("Hello"); }
+private static int Add(int x, int y) { return x + y; }
+```
+
+Если делегат принимает параметры, то в метод _Invoke_ передаются значения для этих параметров.
+
+Следует учитывать, что если делегат пуст, то есть в его списке вызова нет ссылок ни на один из методов (то есть делегат равен **Null**), то при вызове такого делегата мы получим исключение, как, например, в следующем случае:
+
+```cs
+Message mes = null;
+//mes();        // ! Ошибка: делегат равен null
+ 
+Operation op = Add;
+op -= Add;      // делегат op пуст
+op(3, 4);       // !Ошибка: делегат равен null
+```
+
+Поэтому при вызове делегата всегда лучше проверять, не равен ли он **null**. Либо можно использовать метод _Invoke_ и оператор условного **null**:
+
+```cs
+Message mes = null;
+// ошибки нет, делегат просто не вызывается
+mes?.Invoke();        
+ 
+Operation op = Add;
+op -= Add;          // делегат op пуст
+// ошибки нет, делегат просто не вызывается
+op?.Invoke(3, 4);   
+```
+
+Если делегат возвращает некоторое значение, то возвращается значение последнего метода из списка вызова (если в списке вызова несколько методов). Например:
+
+```cs
+delegate int Operation(int x, int y);
+    
+Operation op = Subtract;
+op += Multiply;
+op += Add;
+Console.WriteLine(op(7, 2));    // Add(7,2) = 9
+Console.Read();
+
+private static int Add(int x, int y) { return x + y; }
+private static int Subtract(int x, int y) { return x - y; }
+private static int Multiply(int x, int y) { return x * y; }
+```
+
+### Делегаты как параметры методов
+
+Также делегаты могут быть параметрами методов:
+
+```cs
+delegate void GetMessage();
+ 
+if (DateTime.Now.Hour < 12)
+{
+    Show_Message(GoodMorning);
+}
+else
+{
+    Show_Message(GoodEvening);
+}
+Console.ReadLine();
+
+private static void Show_Message(GetMessage _del)
+{
+    _del?.Invoke();
+}
+private static void GoodMorning()
+{
+    Console.WriteLine("Good Morning");
+}
+private static void GoodEvening()
+{
+    Console.WriteLine("Good Evening");
+}
+```
+
+## Применение делегатов
+
+Выше подробно были рассмотрены делегаты. Однако данные примеры, возможно, не показывают истинной силы делегатов, так как нужные нам методы в данном случае мы можем вызвать и напрямую без всяких делегатов. Однако наиболее сильная сторона делегатов состоит в том, что они позволяют делегировать выполнение некоторому коду извне. И на момент написания программы мы можем не знать, что за код будет выполняться. Мы просто вызываем делегат. А какой метод будет непосредственно выполняться при вызове делегата, будет решаться потом. Например, наши классы будут распространяться в виде отдельной библиотеки классов, которая будет подключаться в проект другого разработчика. И этот разработчик захочет определить какую-то свою логику обработки, но изменить исходный код нашей библиотеки классов он не может. И делегаты как раз предоставляют возможность вызвать некое действие, которое задается извне и которое на момент написания кода может быть неизвестно.
+
+Рассмотрим подробный пример. Пусть у нас есть класс, описывающий счет в банке:
+
+```cs
+class Account
+{
+    int _sum; // Переменная для хранения суммы
+ 
+    public Account(int sum)
+    {
+        _sum = sum;
+    }
+ 
+    public int CurrentSum
+    {
+        get { return _sum; }
+    }
+ 
+    public void Put(int sum)
+    {
+        _sum += sum;
+    }
+ 
+    public void Withdraw(int sum)
+    {
+        if (sum <= _sum)
+        {
+            _sum -= sum;
+        }
+    }
+}
+```
+
+Допустим, в случае вывода денег с помощью метода Withdraw нам надо как-то уведомлять об этом самого клиента и, может быть, другие объекты. Для этого создадим делегат AccountStateHandler. Чтобы использовать делегат, нам надо создать переменную этого делегата, а затем присвоить ему метод, который будет вызываться делегатом.
+
+Итак, добавим в класс Account следующие строки:
+
+```cs
+class Account
+{
+    // Объявляем делегат
+    public delegate void AccountStateHandler(string message);
+    // Создаем переменную делегата
+    AccountStateHandler _del;
+ 
+    // Регистрируем делегат
+    public void RegisterHandler(AccountStateHandler del)
+    {
+        _del = del;
+    }
+     
+    // Далее остальные строки класса Account
+```
+
+Здесь фактически проделываются те же шаги, что были выше, и есть практически все кроме вызова делегата. В данном случае у нас делегат принимает параметр типа **string**. Теперь изменим метод _Withdraw_ следующим образом:
+
+```cs
+public void Withdraw(int sum)
+{
+    if (sum <= _sum)
+    {
+         _sum -= sum;
+ 
+        if (_del != null)
+            _del($"Сумма {sum} снята со счета");
+    }
+    else
+    {
+        if (_del != null)
+            _del("Недостаточно денег на счете");
+    }
+}
+```
+
+Теперь при снятии денег через метод _Withdraw_ мы сначала проверяем, имеет ли делегат ссылку на какой-либо метод (иначе он имеет значение `null`). И если метод установлен, то вызываем его, передавая соответствующее сообщение в качестве параметра.
+
+Теперь протестируем класс в основной программе:
+
+```cs
+class Program
+{
+    static void Main(string[] args)
+    {
+        // создаем банковский счет
+        Account account = new Account(200);
+        // Добавляем в делегат ссылку на метод Show_Message
+        // а сам делегат передается в качестве параметра метода RegisterHandler
+        account.RegisterHandler(new Account.AccountStateHandler(Show_Message));
+        // Два раза подряд пытаемся снять деньги
+        account.Withdraw(100);
+        account.Withdraw(150);
+        Console.ReadLine();
+    }
+    private static void Show_Message(String message)
+    {
+        Console.WriteLine(message);
+    }
+}  
+```
+
+Запустив программу, мы получим два разных сообщения:
+
+```
+Сумма 100 снята со счета
+Недостаточно денег на счете
+```
+
+Таким образом, мы создали механизм обратного вызова для класса Account, который срабатывает в случае снятия денег. Поскольку делегат объявлен внутри класса Account, то чтобы к нему получить доступ, используется выражение Account.AccountStateHandler.
+
+Опять же может возникнуть вопрос: почему бы в коде метода Withdraw() не выводить сообщение о снятии денег? Зачем нужно задействовать какой-то делегат?
+
+Дело в том, что не всегда у нас есть доступ к коду классов. Например, часть классов может создаваться и компилироваться одним человеком, который не будет знать, как эти классы будут использоваться. А использовать эти классы будет другой разработчик.
+
+Так, здесь мы выводим сообщение на консоль. Однако для класса Account не важно, как это сообщение выводится. Классу Account даже не известно, что вообще будет делаться в результате списания денег. Он просто посылает уведомление об этом через делегат.
+
+В результате, если мы создаем консольное приложение, мы можем через делегат выводить сообщение на консоль. Если мы создаем графическое приложение Windows Forms или WPF, то можно выводить сообщение в виде графического окна. А можно не просто выводить сообщение. А, например, записать при списании информацию об этом действии в файл или отправить уведомление на электронную почту. В общем любыми способами обработать вызов делегата. И способ обработки не будет зависеть от класса Account.
+
+Хотя в примере наш делегат принимал адрес на один метод, в действительности он может указывать сразу на несколько методов. Кроме того, при необходимости мы можем удалить ссылки на адреса определенных методов, чтобы они не вызывались при вызове делегата. Итак, изменим в классе Account метод RegisterHandler и добавим новый метод UnregisterHandler, который будет удалять методы из списка методов делегата:
+
+```cs
+// Регистрируем делегат
+public void RegisterHandler(AccountStateHandler del)
+{
+    _del += del; // добавляем делегат
+}
+// Отмена регистрации делегата
+public void UnregisterHandler(AccountStateHandler del)
+{
+    _del -= del; // удаляем делегат
+}
+```
+
+В первом методе объединяет делегаты _del и del в один, который потом присваивается переменной _del. Во втором методе удаляется делегат del. Теперь перейдем к основной программе:
+
+```cs
+Account account = new Account(200);
+Account.AccountStateHandler colorDelegate = new Account.AccountStateHandler(ColorMessage);
+
+// Добавляем в делегат ссылку на методы
+account.RegisterHandler(new Account.AccountStateHandler(ShowMessage));
+account.RegisterHandler(colorDelegate);
+// Два раза подряд пытаемся снять деньги
+account.Withdraw(100);
+account.Withdraw(150);
+
+// Удаляем делегат
+account.UnregisterHandler(colorDelegate);
+account.Withdraw(50);
+        
+Console.ReadLine();
+
+private static void ShowMessage(String message)
+{
+    Console.WriteLine(message);
+}
+private static void ColorMessage(string message)
+{
+    // Устанавливаем красный цвет символов
+    Console.ForegroundColor = ConsoleColor.Red;
+    Console.WriteLine(message);
+    // Сбрасываем настройки цвета
+    Console.ResetColor();
+}
+```
+
+В целях тестирования мы создали еще один метод - _ColorMessage_, который выводит то же самое сообщение только красным цветом. Для первого делегата создается отдельная переменная. Но большой разницы между передачей обоих в метод _account.RegisterHandler_ нет: просто в одном случае мы сразу передаем объект, создаваемый конструктором `account.RegisterHandler(new Account.AccountStateHandler(Show_Message));`
+
+Во втором случае создаем переменную и ее уже передаем в метод `account.RegisterHandler(colorDelegate);`.
+
+В строке `account.UnregisterHandler(colorDelegate);` этот метод удаляется из списка вызовов делегата, поэтому этот метод больше не будет срабатывать. Консольный вывод будет иметь следующую форму:
+
+```
+Сумма 100 снята со счета
+Сумма 100 снята со счета
+Недостаточно денег на счете
+Недостаточно денег на счете
+Сумма 50 снята со счета
+```
+
+## Лямбда-выражения
+
+**Лямбда-выражения** представляют упрощенную запись _анонимных методов_. Лямбда-выражения позволяют создать ёмкие лаконичные методы, которые могут возвращать некоторое значение и которые можно передать в качестве параметров в другие методы.
+
+Ламбда-выражения имеют следующий синтаксис: слева от лямбда-оператора `=>` определяется список параметров, а справа блок выражений, использующий эти параметры: `(список_параметров) => выражение`. Например:
+
+```cs
+delegate int Operation(int x, int y);
+
+Operation operation = (x, y) => x + y;
+
+Console.WriteLine(operation(10, 20));       // 30
+Console.WriteLine(operation(40, 20));       // 60
+Console.Read();
+```
+
+Здесь код `(x, y) => x + y;` представляет собой лямбда-выражение, где _x_ и _y_ - это параметры, а `x + y` - выражение. При этом нам не надо указывать тип параметров, а при возвращении результата не надо использовать оператор **return**.
+
+При этом надо учитывать, что каждый параметр в лямбда-выражении неявно преобразуется в соответствующий параметр делегата, поэтому типы параметров должны быть одинаковыми. Кроме того, количество параметров должно быть таким же, как и у делегата. И возвращаемое значение лямбда-выражений должно быть тем же, что и у делегата. То есть в данном случае использованное лямбда-выражение соответствует делегату **Operation** как по типу возвращаемого значения, так и по типу и количеству параметров.
+
+Если лямбда-выражение принимает один параметр, то скобки вокруг параметра можно опустить:
+
+```cs
+// объявляем делегат, принимающий int и возвращающий int
+delegate int Square(int x);
+
+// объекту делегата присваивается лямбда-выражение
+Square square = i => i * i; 
+
+int z = square(6); // используем делегат
+
+Console.WriteLine(z); // выводит число 36
+Console.Read();
+```
+
+Бывает, что параметров не требуется. В этом случае вместо параметра в лямбда-выражении используются пустые скобки. Также бывает, что лямбда-выражение не возвращает никакого значения:
+
+```cs
+delegate void Hello(); // делегат без параметров
+
+Hello hello1 = () => Console.WriteLine("Hello");
+Hello hello2 = () => Console.WriteLine("Welcome");
+
+hello1();       // Hello
+hello2();       // Welcome
+
+Console.Read();
+```
+
+В данном случае лямда-выражение ничего не возвращает, так как после лямбда-оператора идет действие, которое ничего не возвращает.
+
+Как видно, из примеров выше, нам необязательно указывать тип параметров у лямбда-выражения.
+
+Лямбда-выражения также могут выполнять другие методы:
+
+```cs
+delegate void Hello(); // делегат без параметров
+
+Hello message = () => Show_Message();
+message();
+
+private static void Show_Message()
+{
+    Console.WriteLine("Привет мир!");
+}
+```
+
+### Лямбда-выражения как аргументы методов
+
+Как и делегаты, лямбда-выражения можно передавать в качестве аргументов методу для тех параметров, которые представляют делегат, что довольно удобно:
+
+```cs
+delegate bool IsEqual(int x);
+    
+int[] integers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+    
+// найдем сумму чисел больше 5
+int result1 = Sum(
+    integers, 
+    x => x > 5  // лямбда
+);
+Console.WriteLine(result1); // 30
+    
+// найдем сумму четных чисел
+int result2 = Sum(
+    integers, 
+    x => x % 2 == 0 // лямбда
+);
+Console.WriteLine(result2);  //20
+    
+Console.Read();
+
+
+private static int Sum (int[] numbers, IsEqual func)
+{
+    int result = 0;
+    foreach(int i in numbers)
+    {
+        if (func(i))
+            result += i;
+    }
+    return result;
+}
+```
+
+Метод _Sum_ принимает в качестве параметра массив чисел и делегат _IsEqual_ и возвращает сумму чисел массива в виде объекта **int**. В цикле проходим по всем числам и складываем их. Причем складываем только те числа, для которых делегат _IsEqual func_ возвращает **true**. То есть делегат _IsEqual_ здесь фактически задает условие, которому должны соответствовать значения массива. Но на момент написания метода _Sum_ нам неизвестно, что это за условие.
+
+При вызове метода _Sum_ ему передается массив и лямбда-выражение:
+
+```cs
+int result1 = Sum(integers, x => x > 5);
+```
+
+То есть параметр x здесь будет представлять число, которое передается в делегат:
+
+```cs
+if (func(i))
+```
+
+А выражение `x > 5` представляет условие, которому должно соответствовать число. Если число соответствует этому условию, то лямбда-выражение возвращает **true**, а переданное число складывается с другими числами.
+
+Подобным образом работает второй вызов метода _Sum_, только здесь уже идет проверка числа на четность, то есть если остаток от деления на 2 равен нулю:
+
+```cs
+int result2 = Sum(integers, x => x % 2 == 0);
+```
+---
+
+## Контрольные вопросы
+
+1. [Делегаты](#делегаты)
+1. [Лямбда-выражения](#лямбда-выражения)
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Общие сведения о подпрограммах.](./t5_function.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [Исключения. Null.](./t5_exception.md)

+ 1067 - 0
articles/t5_exception.md

@@ -0,0 +1,1067 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Делегаты, события и лямбды](./t5_delegate.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [Многопоточность. Потоки, асинхронные вычисления](./t5_thread_async.md)
+
+<!-- https://metanit.com/sharp/tutorial/2.14.php -->
+
+# Исключения
+
+В любой, особенно большой, программе могут возникать ошибки, приводящие к её неработоспособности или к тому, что программа делает не то, что должна. Причин возникновения ошибок много.
+
+Программист может сделать ошибку в употреблении самого языка программирования. Другими словами, выразиться так, как выражаться не положено. Например, начать имя переменной с цифры или забыть поставить двоеточие в заголовке сложной инструкции. Подобные ошибки называют **синтаксическими**, они нарушают синтаксис и пунктуацию языка. **IDE**, встретив ошибочное выражение, не знает как его интерпретировать. Поэтому  выводит соответствующее сообщение, указав на место возникновения ошибки.
+
+Но случаются ошибки, которые происходят во время выполнения программы, например, деление на 0 или попытка открыть несуществующий файл. В таких случаях программа "выбрасывает" исключение.
+
+На этот случай в языках программирования, в том числе **C#**, существует специальный оператор, позволяющий перехватывать возникающие исключения и обрабатывать их так, чтобы программа продолжала работать или корректно завершала свою работу.
+
+## Конструкция try..catch..finally
+
+```cs
+try
+{
+     
+}
+catch
+{
+     
+}
+finally
+{
+     
+}
+```
+
+При использовании блока `try...catch...finally` вначале выполняются все инструкции в блоке **try**. Если в этом блоке не возникло исключений, то после его выполнения начинает выполняться блок **finally**. И затем конструкция `try..catch...finally` завершает свою работу.
+
+Если же в блоке **try** вдруг возникает исключение, то обычный порядок выполнения останавливается, и среда выполнения кода начинает искать блок **catch**, который может обработать данное исключение. Если нужный блок **catch** найден, то он выполняется, и после его завершения выполняется блок **finally**.
+
+Если нужный блок **catch** не найден, то при возникновении исключения программа аварийно завершает свое выполнение.
+
+Рассмотрим следующий пример:
+
+```cs
+int x = 5;
+int y = x / 0;
+Console.WriteLine($"Результат: {y}");
+Console.WriteLine("Конец программы");
+Console.Read();
+```
+
+В данном случае происходит деление числа на `0`, что приведет к генерации исключения. И при запуске приложения в консоли увидим сообщение об ошибке:
+
+```
+Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.
+   at Program.<Main>$(String[] args) in /home/kei/RiderProjects/tryCatch/Program.cs:line 2
+
+Process finished with exit code 134.
+```
+
+Здесь мы видим, что возникло исключение, которое представляет тип `System.DivideByZeroException`, то есть попытка деления на ноль. Ниже указано в каком файле и в какой строке файла произошло это исключение.
+
+Чтобы избежать подобного аварийного завершения программы, следует использовать для обработки исключений конструкцию `try...catch...finally`. Так, перепишем пример следующим образом:
+
+```cs
+try
+{
+    int x = 5;
+    int y = x / 0;
+    Console.WriteLine($"Результат: {y}");
+}
+catch
+{
+    Console.WriteLine("Возникло исключение!");
+}
+finally
+{
+    Console.WriteLine("Блок finally");
+}
+Console.WriteLine("Конец программы");
+Console.Read();
+```
+
+В данном случае у нас опять же возникнет исключение в блоке **try**, так как мы пытаемся разделить на ноль. И дойдя до строки `int y = x / 0;` выполнение программы остановится. CLR найдет блок **catch** и передаст управление этому блоку.
+
+После блока **catch** будет выполняться блок **finally**.
+
+```
+Возникло исключение!
+Блок finally
+Конец программы
+```
+
+Таким образом, программа по-прежнему не будет выполнять деление на ноль и соответственно не будет выводить результат этого деления, но теперь она не будет аварийно завершаться, а исключение будет обрабатываться в блоке **catch**.
+
+Следует отметить, что в этой конструкции обязателен блок **try**. При наличии блока **catch** мы можем опустить блок **finally**:
+
+```cs
+try
+{
+    int x = 5;
+    int y = x / 0;
+    Console.WriteLine($"Результат: {y}");
+}
+catch
+{
+    Console.WriteLine("Возникло исключение!");
+}
+```
+
+И, наоборот, при наличии блока **finally** мы можем опустить блок **catch** и не обрабатывать исключение:
+
+```cs
+try
+{
+    int x = 5;
+    int y = x / 0;
+    Console.WriteLine($"Результат: {y}");
+}
+finally
+{
+    Console.WriteLine("Блок finally");
+}
+```
+
+Однако, хотя с точки зрения синтаксиса **C#** такая конструкция вполне корректна, тем не менее, поскольку CLR не сможет найти нужный блок **catch**, то исключение не будет обработано, и программа аварийно завершится.
+
+## Обработка исключений и условные конструкции
+
+Ряд исключительных ситуаций может быть предвиден разработчиком. Например, пусть программа предусматривает ввод числа и вывод его квадрата:
+
+```cs
+Console.WriteLine("Введите число");
+int x = Int32.Parse(Console.ReadLine());
+
+x *= x;
+Console.WriteLine("Квадрат числа: " + x);
+Console.Read();
+```
+
+Если пользователь введет не число, а строку, какие-то другие символы, то программа выпадет в ошибку. С одной стороны, здесь как раз та ситуация, когда можно применить блок `try...catch`, чтобы обработать возможную ошибку. Однако гораздо оптимальнее было бы проверить допустимость преобразования:
+
+```cs
+Console.WriteLine("Введите число");
+int x;
+string input = Console.ReadLine();
+
+if (Int32.TryParse(input, out x))
+{
+    x *= x;
+    Console.WriteLine("Квадрат числа: " + x);
+}
+else
+{
+    Console.WriteLine("Некорректный ввод");
+}
+
+Console.Read();
+```
+
+Метод `Int32.TryParse()` возвращает **true**, если преобразование можно осуществить, и **false** - если нельзя. При допустимости преобразования переменная `x` будет содержать введенное число. Так, не используя `try...catch` можно обработать возможную исключительную ситуацию.
+
+С точки зрения производительности использование блоков `try...catch` более накладно, чем применение условных конструкций. ~~Поэтому по возможности вместо `try..catch` лучше использовать условные конструкции на проверку исключительных ситуаций.~~ Сейчас уклон при разработке не в скорость работы программы, а в скорость разработки, поэтому рекомендуется применять блоки `try...catch...finally`, т.к. при этом получается более короткий и наглядный код.
+
+## Блок catch и фильтры исключений
+
+### Определение блока catch
+
+За обработку исключения отвечает блок **catch**, который может иметь следующие формы:
+
+```cs
+catch
+{
+    // выполняемые инструкции
+}
+```
+
+Обрабатывает любое исключение, которое возникло в блоке **try**. Выше уже был продемонстрирован пример подобного блока.
+
+```cs
+catch (тип_исключения)
+{
+    // выполняемые инструкции
+}
+```
+
+Такой блок обрабатывает только те исключения, которые соответствуют типу, указаному в скобках после оператора **catch**.
+
+Например, обработаем только исключения типа *DivideByZeroException*:
+
+```cs
+try
+{
+    int x = 5;
+    int y = x / 0;
+    Console.WriteLine($"Результат: {y}");
+}
+catch(DivideByZeroException)
+{
+    Console.WriteLine("Возникло исключение DivideByZeroException");
+}
+```
+
+Однако если в блоке **try** возникнут исключения каких-то других типов, отличных от *DivideByZeroException*, то они не будут обработаны.
+
+```cs
+catch (тип_исключения имя_переменной)
+{
+    // выполняемые инструкции
+}
+```
+
+В таком варианте обрабатывает только те исключения, которые соответствуют типу, указаному в скобках после оператора **catch**. А вся информация об исключении помещается в переменную данного типа. Например:
+
+```cs
+try
+{
+    int x = 5;
+    int y = x / 0;
+    Console.WriteLine($"Результат: {y}");
+}
+catch(DivideByZeroException ex)
+{
+    Console.WriteLine($"Возникло исключение {ex.Message}");
+}
+```
+
+Фактически этот случай аналогичен предыдущему за тем исключением, что здесь используется переменная. В данном случае в переменную *ex*, которая представляет тип *DivideByZeroException*, помещается информация о возникшем исключени. И с помощью свойства _Message_ мы можем получить сообщение об ошибке.
+
+Если нам не нужна информация об исключении, то переменную можно не использовать как в предыдущем случае.
+
+### Фильтры исключений
+
+Фильтры исключений позволяют обрабатывать исключения в зависимости от определенных условий. Для их применения после выражения **catch** идет выражение **when**, после которого в скобках указывается условие:
+
+```cs
+catch when(условие)
+{
+     
+}
+```
+
+В этом случае обработка исключения в блоке **catch** производится только в том случае, если условие в выражении **when** истинно. Например:
+
+```cs
+int x = 1;
+int y = 0;
+ 
+try
+{
+    int result = x / y;
+}
+catch(DivideByZeroException) when (y==0 && x == 0)
+{
+    Console.WriteLine("y не должен быть равен 0");
+}
+catch(DivideByZeroException ex)
+{
+    Console.WriteLine(ex.Message);
+}
+```
+
+В данном случае будет выброшено исключение, так как `y=0`. Здесь два блока **catch**, и оба они обрабатывают исключения типа *DivideByZeroException*, то есть по сути все исключения, генерируемые при делении на ноль. Но поскольку для первого блока указано условие `y == 0 && x == 0`, то оно не будет обрабатывать исключение - условие, указанное после оператора **when** возвращает *false*. Поэтому CLR будет дальше искать соответствующие блоки **catch** далее и для обработки исключения выберет второй блок **catch**. В итоге если мы уберем второй блок **catch**, то исключение вобще не будет обрабатываться.
+
+## Типы исключений. Класс Exception
+
+Базовым для всех типов исключений является тип **Exception**. Этот тип определяет ряд свойств, с помощью которых можно получить информацию об исключении.
+
+* **InnerException**: хранит информацию об исключении, которое послужило причиной текущего исключения
+
+* **Message**: хранит сообщение об исключении, текст ошибки
+
+* **Source**: хранит имя объекта или сборки, которое вызвало исключение
+
+* **StackTrace**: возвращает строковое представление стека вызывов, которые привели к возникновению исключения
+
+* **TargetSite**: возвращает метод, в котором и было вызвано исключение
+
+Например, обработаем исключения типа **Exception**:
+
+```cs
+try
+{
+    int x = 5;
+    int y = x / 0;
+    Console.WriteLine($"Результат: {y}");
+}
+catch (Exception ex)
+{
+    Console.WriteLine($"Исключение: {ex.Message}");
+    Console.WriteLine($"Метод: {ex.TargetSite}");
+    Console.WriteLine($"Трассировка стека: {ex.StackTrace}");
+}
+
+Console.Read();
+```
+
+```sh
+Исключение: Попытка деления на нуль.
+Метод: Void Main(System.String[])
+Трассировка стека:    в oap_labs.Program.Main(String[] args) в C:\Users\John\source\repos\oap_labs\oap_labs\Program.cs:строка 16
+```
+
+Так как тип **Exception** является базовым типом для всех исключений, то выражение `catch (Exception ex)` будет обрабатывать все исключения, которые могут возникнуть.
+
+Но также есть более специализированные типы исключений, которые предназначены для обработки каких-то определенных видов исключений. Их довольно много, я приведу лишь некоторые:
+
+* **DivideByZeroException**: представляет исключение, которое генерируется при делении на ноль
+
+* **ArgumentOutOfRangeException**: генерируется, если значение аргумента находится вне диапазона допустимых значений
+
+* **ArgumentException**: генерируется, если в метод для параметра передается некорректное значение
+
+* **IndexOutOfRangeException**: генерируется, если индекс элемента массива или коллекции находится вне диапазона допустимых значений
+
+* **InvalidCastException**: генерируется при попытке произвести недопустимые преобразования типов
+
+* **NullReferenceException**: генерируется при попытке обращения к объекту, который равен null (то есть по сути неопределен)
+
+И при необходимости мы можем разграничить обработку различных типов исключений, включив дополнительные блоки **catch**:
+
+```cs
+try
+{
+    int[] numbers = new int[4];
+    numbers[7] = 9;     // IndexOutOfRangeException
+
+    int x = 5;
+    int y = x / 0;  // DivideByZeroException
+    Console.WriteLine($"Результат: {y}");
+}
+catch (DivideByZeroException)
+{
+    Console.WriteLine("Возникло исключение DivideByZeroException");
+}
+catch (IndexOutOfRangeException ex)
+{
+    Console.WriteLine(ex.Message);
+}
+            
+Console.Read();
+```
+
+В данном случае блоки **catch** обрабатывают исключения типов *IndexOutOfRangeException* и *DivideByZeroException*. Когда в блоке **try** возникнет исключение, то CLR будет искать нужный блок **catch** для обработки исключения. Так, в данном случае на строке
+
+```cs
+numbers[7] = 9;
+```
+
+происходит обращение к 7-му элементу массива. Однако поскольку в массиве только 4 элемента, то мы получим исключение типа *IndexOutOfRangeException*. CLR найдет блок **catch**, который обрабатывает данное исключение, и передаст ему управление.
+
+Следует отметить, что в данном случае в блоке **try** есть ситуация для генерации второго исключения - деление на ноль. Однако поскольку после генерации *IndexOutOfRangeException* управление переходит в соответствующий блок **catch**, то деление на ноль `int y = x / 0` в принципе не будет выполняться, поэтому исключение типа *DivideByZeroException* никогда не будет сгенерировано.
+
+Рассмотрим другую ситуацию:
+
+```cs
+try
+{
+    object obj = "you";
+    int num = (int)obj;     // InvalidCastException
+    Console.WriteLine($"Результат: {num}");
+}
+catch (DivideByZeroException)
+{
+    Console.WriteLine("Возникло исключение DivideByZeroException");
+}
+catch (IndexOutOfRangeException)
+{
+    Console.WriteLine("Возникло исключение IndexOutOfRangeException");
+}
+            
+Console.Read();
+```
+
+В данном случае в блоке **try** генерируется исключение типа *InvalidCastException*, однако соответствующего блока **catch** для обработки данного исключения нет. Поэтому программа аварийно завершит свое выполнение.
+
+Мы также можем определить для *InvalidCastException* свой блок **catch**, однако суть в том, что теоретически в коде могут быть сгенерированы сами различные типы исключений. А определять для всех типов исключений блоки **catch**, если обработка исключений однотипна, не имеет смысла. И в этом случае мы можем определить блок **catch** для базового типа *Exception*:
+
+```cs
+try
+{
+    object obj = "you";
+    int num = (int)obj;     // InvalidCastException
+    Console.WriteLine($"Результат: {num}");
+}
+catch (DivideByZeroException)
+{
+    Console.WriteLine("Возникло исключение DivideByZeroException");
+}
+catch (IndexOutOfRangeException)
+{
+    Console.WriteLine("Возникло исключение IndexOutOfRangeException");
+}
+catch (Exception ex)
+{
+    Console.WriteLine($"Исключение: {ex.Message}");
+}  
+Console.Read();
+```
+
+И в данном случае блок `catch (Exception ex){}` будет обрабатывать все исключения кроме **DivideByZeroException** и **IndexOutOfRangeException**. При этом блоки **catch** для более общих, более базовых исключений следует помещать в конце - после блоков **catch** для более конкретный, специализированных типов. Так как CLR выбирает для обработки исключения первый блок **catch**, который соответствует типу сгенерированного исключения. Поэтому в данном случае сначала обрабатывается исключение **DivideByZeroException** и **IndexOutOfRangeException**, и только потом *Exception* (так как **DivideByZeroException** и **IndexOutOfRangeException** наследуется от класса **Exception**).
+
+## Создание классов исключений
+
+Если нас не устраивают встроенные типы исключений, то мы можем создать свои типы. Базовым классом для всех исключений является класс **Exception**, соответственно для создания своих типов мы можем унаследовать данный класс.
+
+Допустим, у нас в программе будет ограничение по возрасту:
+
+```cs
+try
+{
+    Person p = new Person { Name = "Tom", Age = 17 };
+}
+catch (Exception ex)
+{
+    Console.WriteLine($"Ошибка: {ex.Message}");
+}
+Console.Read();
+
+class Person
+{
+    private int age;
+    public string Name { get; set; }
+    public int Age
+    {
+        get { return age; }
+        set
+        {
+            if (value < 18)
+            {
+                throw new Exception("Лицам до 18 регистрация запрещена");
+            }
+            else
+            {
+                age = value;
+            }
+        }
+    }
+}
+```
+
+В классе **Person** при установке возраста происходит проверка, и если возраст меньше `18` лет, то выбрасывается исключение. Класс **Exception** принимает в конструкторе в качестве параметра строку, которая затем передается в его свойство _Message_.
+
+Но иногда удобнее использовать свои классы исключений. Например, в какой-то ситуации мы хотим обработать определенным образом только те исключения, которые относятся к классу **Person**. Для этих целей мы можем сделать специальный класс **PersonException**:
+
+```cs
+class PersonException : Exception
+{
+    public PersonException(string message): base(message)
+    { }
+}
+```
+
+По сути класс кроме пустого конструктора ничего не имеет, и то в конструкторе мы просто обращаемся к конструктору базового класса **Exception**, передавая в него строку *message*. Но теперь мы можем изменить класс **Person**, чтобы он выбрасывал исключение именно этого типа и соответственно в основной программе обрабатывать это исключение:
+
+```cs
+try
+{
+    Person p = new Person { Name = "Tom", Age = 17 };
+}
+catch (PersonException ex)
+{
+    Console.WriteLine("Ошибка: " + ex.Message);
+}
+Console.Read();
+
+class Person
+{
+    private int age;
+    public int Age
+    {
+        get { return age; }
+        set
+        {
+            if (value < 18)
+                throw new PersonException("Лицам до 18 регистрация запрещена");
+            else
+                age = value;
+        }
+    }
+}
+```
+
+Однако необязательно наследовать свой класс исключений именно от типа **Exception**, можно взять какой-нибудь другой производный тип. Например, в данном случае мы можем взять тип **ArgumentException**, который представляет исключение, генерируемое в результате передачи аргументу метода некорректного значения:
+
+```cs
+class PersonException : ArgumentException
+{
+    public PersonException(string message)
+        : base(message)
+    { }
+}
+```
+
+Каждый тип исключений может определять какие-то свои свойства. Например, в данном случае мы можем определить в классе свойство для хранения устанавливаемого значения:
+
+```cs
+class PersonException : ArgumentException
+{
+    public int Value { get;}
+    public PersonException(string message, int val)
+        : base(message)
+    {
+        Value = val;
+    }
+}
+```
+
+В конструкторе класса мы устанавливаем это свойство и при обработке исключения мы его можем получить:
+
+```cs
+class Person
+{
+    public string Name { get; set; }
+    private int age;
+    public int Age
+    {
+        get { return age; }
+        set
+        {
+            if (value < 18)
+                throw new PersonException(
+                    "Лицам до 18 регистрация запрещена",
+                    value);
+            else
+                age = value;
+        }
+    }
+}
+
+try
+{
+    Person p = new Person { Name = "Tom", Age = 13 };
+}
+catch (PersonException ex)
+{
+    Console.WriteLine($"Ошибка: {ex.Message}");
+    Console.WriteLine($"Некорректное значение: {ex.Value}");
+}
+Console.Read();
+```
+
+## Поиск блока catch при обработке исключений
+
+Если код, который вызывает исключение, не размещен в блоке **try** или помещен в конструкцию `try..catch`, которая не содержит соответствующего блока **catch** для обработки возникшего исключения, то система производит поиск соответствующего обработчика исключения в стеке вызовов.
+
+Например, рассмотрим следующую программу:
+
+```cs
+try
+{
+    TestClass.Method1();
+}
+catch (DivideByZeroException ex)
+{
+    Console.WriteLine($"Catch в Main : {ex.Message}");
+}
+finally
+{
+    Console.WriteLine("Блок finally в Main");
+}
+Console.WriteLine("Конец метода Main");
+Console.Read();
+
+class TestClass
+{
+    public static void Method1()
+    {
+        try
+        {
+            Method2();
+        }
+        catch (IndexOutOfRangeException ex)
+        {
+            Console.WriteLine($"Catch в Method1 : {ex.Message}");
+        }
+        finally
+        {
+            Console.WriteLine("Блок finally в Method1");
+        }
+        Console.WriteLine("Конец метода Method1");
+    }
+    static void Method2()
+    {
+        try
+        {
+            int x = 8;
+            int y = x / 0;
+        }
+        finally
+        {
+            Console.WriteLine("Блок finally в Method2");
+        }
+        Console.WriteLine("Конец метода Method2");
+    }
+}
+```
+
+В данном случае стек вызовов выглядит следующим образом: метод *Method1* вызывает метод *Method2*. И в методе *Method2* генерируется исключение **DivideByZeroException**. Визуально стек вызовов можно представить следующим образом:
+
+```sh
+Блок finally в Method2
+Блок finally в Method1
+Catch в Main : Attempted to divide by zero.
+Блок finally в Main
+Конец метода Main
+```
+
+Внизу стека метод *Main*, с которого началось выполнение, и на самом верху метод *Method2*.
+
+Что будет происходить в данном случае при генерации исключения?
+
+Метод *Main* вызывает метод *Method1*, а тот вызывает метод *Method2*, в котором генерируется исключение *DivideByZeroException*.
+
+Система видит, что код, который вызывал исключение, помещен в конструкцию `try...`
+
+```cs
+try
+{
+    int x = 8;
+    int y = x / 0;
+}
+finally
+{
+    Console.WriteLine("Блок finally в Method2");
+}
+```
+
+Система ищет в этой конструкции блок **catch**, который обрабатывает исключение *DivideByZeroException*. Однако такого блока **catch** нет.
+
+Система опускается в стеке вызовов в метод *Method1*, который вызывал *Method2*. Здесь вызов *Method2* помещен в конструкцию `try..catch`
+
+
+```cs
+try
+{
+    Method2();
+}
+catch (IndexOutOfRangeException ex)
+{
+    Console.WriteLine($"Catch в Method1 : {ex.Message}");
+}
+finally
+{
+    Console.WriteLine("Блок finally в Method1");
+}
+```
+
+Система также ищет в этой конструкции блок **catch**, который обрабатывает исключение **DivideByZeroException**. Однако здесь также подобный блок **catch** отсутствует.
+
+Система далее опускается в стеке вызовов в метод *Main*, который вызывал *Method1*. Здесь вызов *Method1* помещен в конструкцию `try..catch`
+
+```cs
+try
+{
+    TestClass.Method1();
+}
+catch (DivideByZeroException ex)
+{
+    Console.WriteLine($"Catch в Main : {ex.Message}");
+}
+finally
+{
+    Console.WriteLine("Блок finally в Main");
+}
+```
+
+Система снова ищет в этой конструкции блок **catch**, который обрабатывает исключение **DivideByZeroException**. И в данном случае ткой блок найден.
+
+Система наконец нашла нужный блок **catch** в методе *Main*, для обработки исключения, которое возникло в методе *Method2* - то есть к начальному методу, где непосредственно возникло исключение. Но пока данный блок **catch** НЕ выполняется. Система поднимается обратно по стеку вызовов в самый верх в метод *Method2* и выполняет в нем блок **finally**:
+
+```cs
+finally
+{
+    Console.WriteLine("Блок finally в Method2");
+}
+```
+
+Далее система возвращается по стеку вызовов вниз в метод *Method1* и выполняет в нем блок **finally**:
+
+```cs
+finally
+{
+    Console.WriteLine("Блок finally в Method1");
+}
+```
+
+Затем система переходит по стеку вызовов вниз в метод *Main* и выполняет в нем найденный блок **catch** и последующий блок **finally**:
+
+```cs
+catch (DivideByZeroException ex)
+{
+    Console.WriteLine($"Catch в Main : {ex.Message}");
+}
+finally
+{
+    Console.WriteLine("Блок finally в Main");
+}
+```
+
+Далее выполняется код, который идет в методе *Main* после конструкции `try..catch`:
+
+```cs
+Console.WriteLine("Конец метода Main");
+```
+
+Стоит отметить, что код, который идет после конструкции `try...catch` в методах *Method1* и *Method2*, не выполняется, потому что обработчик исключения найден именно в методе *Main*.
+
+Консольный вывод программы:
+
+```
+Блок finally в Method2
+Блок finally в Method1
+Catch в Main: Попытка деления на нуль.
+Блок finally в Main
+Конец метода Main
+```
+
+## Генерация исключения и оператор throw
+
+Обычно система сама генерирует исключения при определенных ситуациях, например, при делении числа на ноль. Но язык **C#** также позволяет генерировать исключения вручную с помощью оператора **throw**. То есть с помощью этого оператора мы сами можем создать исключение и вызвать его в процессе выполнения.
+
+Например, в нашей программе происходит ввод строки, и мы хотим, чтобы, если длина строки будет больше шести символов, возникало исключение:
+
+```cs
+try
+{
+    Console.Write("Введите строку: ");
+    string message = Console.ReadLine();
+    if (message.Length > 6)
+    {
+        throw new Exception(
+            "Длина строки больше 6 символов");
+    }
+}
+catch (Exception e)
+{
+    Console.WriteLine($"Ошибка: {e.Message}");
+}
+Console.Read();
+```
+
+После оператора **throw** указывается объект исключения, через конструктор которого мы можем передать сообщение об ошибке. Естественно вместо типа **Exception** мы можем использовать объект любого другого типа исключений.
+
+Затем в блоке **catch** сгенерированное нами исключение будет обработано.
+
+Подобным образом мы можем генерировать исключения в любом месте программы. Но существует также и другая форма использования оператора **throw**, когда после данного оператора не указывается объект исключения. В подобном виде оператор **throw** может использоваться только в блоке **catch**:
+
+```cs
+try
+{
+    try
+    {
+        Console.Write("Введите строку: ");
+        string message = Console.ReadLine();
+        if (message.Length > 6)
+        {
+            throw new Exception(
+                "Длина строки больше 6 символов");
+        }
+    }
+    catch
+    {
+        Console.WriteLine("Возникло исключение");
+        throw;
+    }
+}
+catch (Exception ex)
+{
+    Console.WriteLine(ex.Message);
+}
+```
+
+В данном случае при вводе строки с длиной больше 6 символов возникнет исключение, которое будет обработано внутренним блоком **catch**. Однако поскольку в этом блоке используется оператор **throw**, то исключение будет передано дальше внешнему блоку **catch**.
+
+## NULL
+
+Обычные типы значений не могут иметь значение **NULL**. Но вы можете создать специальные типы, допускающие значения **NULL**, добавив символ `?` после имени типа. Например, тип `int?` является типом **int**, который может иметь значение **NULL**. Типы, допускающие значение **NULL**, особенно полезны при передаче данных в базы данных, где могут использоваться числовые значения **NULL**, и из таких баз данных.
+
+**Тип значений, допускающий значение NULL** , или `T?`, представляет все значения своего базового типа значения `T`, а также дополнительное значение **NULL**. Например, можно присвоить переменной `bool?` любое из следующих трех значений: **true**, **false** или **null**.
+
+Тип значения, допускающий значение **NULL**, следует использовать, когда нужно представить неопределенное значение его базового типа. Например, логическая переменная (или **bool**) может иметь только значения **true** или **false**. Однако в некоторых приложениях значение переменной может быть неопределенным или отсутствовать. Например, поле базы данных может содержать значение **true** или **false** либо вообще никакого значения, то есть **NULL**. В этом сценарии можно использовать тип `bool?`.
+
+### Назначение и объявление
+
+Так как тип значения можно неявно преобразовать в соответствующий тип значения, допускающий значение **NULL**, вы назначаете значение переменной такого типа значения так же, как для базового типа значения. Вы можете также присвоить значение **null**. Пример:
+
+```cs
+double? pi = 3.14;
+char? letter = 'a';
+
+int m2 = 10;
+int? m = m2;
+
+bool? flag = null;
+
+// An array of a nullable value type:
+int?[] arr = new int?[10];
+```
+
+Значение по умолчанию для типа значения, допускающего значение **NULL**, равно `null`.
+
+### Проверка экземпляра типа значения, допускающего значение NULL
+
+Начиная с версии C# 7.0 можно использовать оператор **is** с шаблоном типа как для проверки экземпляра типа, допускающего значение **NULL**, для **null**, так и для извлечения значения базового типа:
+
+```cs
+int? a = 42;
+if (a is int valueOfA)
+{
+    Console.WriteLine($"a is {valueOfA}");
+}
+else
+{
+    Console.WriteLine("a does not have a value");
+}
+```
+
+Вывод:
+
+```
+a is 42
+```
+
+Вы всегда можете использовать следующие свойства, чтобы проверить и получить значение переменной типа, допускающего значение **NULL**:
+
+* **Nullable<T>.HasValue** указывает, имеет ли экземпляр типа, допускающего значение NULL, значение своего базового типа.
+* **Nullable<T>.Value** возвращает значение базового типа, если *HasValue* имеет значение **true**. Если *HasValue* имеет значение **false**, свойство *Value* выдает исключение *InvalidOperationException*.
+
+В следующем примере используется свойство *HasValue*, чтобы проверить, содержит ли переменная значение, перед его отображением:
+
+```cs
+int? b = 10;
+if (b.HasValue)
+{
+    Console.WriteLine($"b is {b.Value}");
+}
+else
+{
+    Console.WriteLine("b does not have a value");
+}
+```
+
+Можно также сравнить переменную типа значения, допускающего значение **NULL**, с **null** вместо использования свойства *HasValue*, как показано в следующем примере:
+
+```cs
+int? c = 7;
+if (c != null)
+{
+    Console.WriteLine($"c is {c.Value}");
+}
+else
+{
+    Console.WriteLine("c does not have a value");
+}
+```
+
+### Преобразование из типа значения, допускающего значение NULL, в базовый тип
+
+Если необходимо присвоить значение типа, допускающего значение **NULL**, переменной типа значения, не допускающего значения **NULL**, может потребоваться указать значение, назначаемое вместо **null**. Для этого используйте оператор объединения со значением **NULL** `??` (можно также применить метод `Nullable<T>.GetValueOrDefault(T)` для той же цели):
+
+```cs
+int? a = 28;
+int b = a ?? -1;
+Console.WriteLine($"b is {b}");  // output: b is 28
+
+int? c = null;
+int d = c ?? -1;
+Console.WriteLine($"d is {d}");  // output: d is -1
+```
+
+Вы можете также явно привести тип значения, допускающий значение **NULL**, к типу, не допускающему значение **NULL**, как показано в примере ниже.
+
+```cs
+int? n = null;
+
+//int m1 = n;    // Doesn't compile
+int n2 = (int)n; // Compiles, but throws an exception if n is null
+```
+
+Во время выполнения, если значение типа значения, допускающего значение **NULL**, равно **null**, явное приведение вызывает исключение *InvalidOperationException*.
+
+### Операторы с нулификацией
+
+Предопределенные унарные и бинарные операторы или любые перегруженные операторы, поддерживаемые типом значения `T`, также поддерживаются соответствующим типом значения, допускающим значение **NULL**, т. е. `T?`. Эти операторы, также называемые операторами с нулификацией , возвращают значение **null**, если один или оба операнда имеют значение **null**. В противном случае оператор использует содержащиеся значения операндов для вычисления результата. Пример:
+
+```cs
+int? a = 10;
+int? b = null;
+int? c = 10;
+
+a++;        // a is 11
+a = a * c;  // a is 110
+a = a + b;  // a is null
+```
+
+Для операторов сравнения `<`, `>`, `<=` и `>=`, если один или оба операнда равны **null**, результат будет равен **false**. В противном случае сравниваются содержащиеся значения операндов. Тут важно не полагать, что если какая-то операция сравнения (например, `<=`) возвращает **false**, то противоположное сравнение (`>`) обязательно вернет **true**. В следующем примере показано, что 10
+не больше и не равно значению null, не меньше чем null.
+
+```cs
+int? a = 10;
+Console.WriteLine($"{a} >= null is {a >= null}");
+Console.WriteLine($"{a} < null is {a < null}");
+Console.WriteLine($"{a} == null is {a == null}");
+// Output:
+// 10 >= null is False
+// 10 < null is False
+// 10 == null is False
+
+int? b = null;
+int? c = null;
+Console.WriteLine($"null >= null is {b >= c}");
+Console.WriteLine($"null == null is {b == c}");
+// Output:
+// null >= null is False
+// null == null is True
+```
+
+Для оператора равенства `==`, если оба операнда равны **null**, результат будет равен **true**. Если один из операндов равен **null**, результат будет равен **false**. В противном случае сравниваются содержащиеся значения операндов.
+
+Для оператора неравенства `!=`, если оба операнда равны **null**, результат будет равен **false**. Если один из операндов равен **null**, результат будет равен **true**. В противном случае сравниваются содержащиеся значения операндов.
+
+Если между двумя типами данных определено пользовательское преобразование типов, то это же преобразование можно также использовать между соответствующими типами, допускающими значение **NULL**.
+
+### Упаковка-преобразование и распаковка-преобразование
+
+Экземпляр типа значения, допускающего значение **NULL**, `T?` упакован следующим образом:
+* Если *HasValue* возвращает **false**, создается пустая ссылка.
+* Если *HasValue* возвращает **true**, упаковывается соответствующее значение базового типа `T`, а не экземпляр `Nullable<T>`.
+
+Можно распаковать упакованный тип значения `T` в соответствующий тип, допускающий значение **NULL**, `T?`, как показано в следующем примере:
+
+```cs
+int a = 41;
+object aBoxed = a;
+int? aNullable = (int?)aBoxed;
+Console.WriteLine($"Value of aNullable: {aNullable}");
+
+object aNullableBoxed = aNullable;
+if (aNullableBoxed is int valueOfA)
+{
+    Console.WriteLine($"aNullableBoxed is boxed int: {valueOfA}");
+}
+// Output:
+// Value of aNullable: 41
+// aNullableBoxed is boxed int: 41
+```
+
+### Оператор ??
+
+Оператор `??` называется оператором null-объединения. Он применяется для установки значений по умолчанию для типов, которые допускают значение **null**. Оператор `??` возвращает левый операнд, если этот операнд не равен **null**. Иначе возвращается правый операнд. При этом левый операнд должен принимать **null**. Посмотрим на примере:
+
+```cs
+object x = null;
+object y = x ?? 100;  // равно 100, так как x равен null
+ 
+object z = 200;
+object t = z ?? 44; // равно 200, так как z не равен null
+```
+
+Но мы не можем написать следующим образом:
+
+```cs
+int x = 44;
+int y = x ?? 100;
+```
+
+Здесь переменная x представляет значимый тип **int** и не может принимать значение **null**, поэтому в качестве левого операнда в операции `??` она использоваться не может.
+
+### Оператор условного null
+
+Иногда при работе с объектами, которые принимают значение **null**, мы можем столкнуться с ошибкой: мы пытаемся обратиться к объекту, а этот объект равен **null**. Например, пусть у нас есть следующая система классов:
+
+```cs
+class User
+{
+    public Phone Phone { get; set; }
+}
+ 
+class Phone
+{
+    public Company Company { get; set; }
+}
+ 
+class Company
+{
+    public string Name { get; set; }
+}
+```
+
+Объект *User* содержит ссылку на объект *Phone*, а объект *Phone* содержит ссылку на объект *Company*, поэтому теоретически мы можем получить из объекта *User* название компании, например, так:
+
+```cs
+User user = new User();
+Console.WriteLine(user.Phone.Company.Name);
+```
+
+В данном случае свойство *Phone* не определено, будет по умолчанию иметь значение **null**. Поэтому мы столкнемся с исключением *NullReferenceException*. Чтобы избежать этой ошибки мы могли бы использовать условную конструкцию для проверки на **null**:
+
+```cs
+User user = new User();
+ 
+if(user!=null)
+{
+    if(user.Phone!=null)
+    {
+        if (user.Phone.Company != null)
+        {
+            string companyName = user.Phone.Company.Name;
+            Console.WriteLine(companyName);
+        }
+    }
+}
+```
+
+Получается многоэтажная конструкция, но на самом деле ее можно сократить:
+
+```cs
+if(user!=null && user.Phone!=null && user.Phone.Company!=null)
+{
+    string companyName = user.Phone.Company.Name;
+    Console.WriteLine(companyName);
+}
+```
+
+Если *user* не равно **null**, то проверяется следующее выражение `user.Phone!=null` и так далее. Конструкция намного проще, но все равно получается довольно большой. И чтобы ее упростить, в C# можно использовать **оператор условного null** (Null-Conditional Operator):
+
+```cs
+string companyName = user?.Phone?.Company?.Name;
+```
+
+Выражение `?`. и представляет **оператор условного null**. Здесь последовательно проверяется равен ли объект *user* и вложенные объекты значению **null**. Если же на каком-то этапе один из объектов окажется равным **null**, то *companyName* будет иметь значение по умолчанию, то есть **null**.
+
+И в этом случае мы можем пойти дальше и применить операцию `??` для установки значения по умолчанию, если название компании не установлено:
+
+```cs
+User user = new User();
+string companyName = user?.Phone?.Company?.Name ?? "не установлено";
+Console.WriteLine(companyName);
+```
+
+---
+
+## Задание на дом:
+
+Реализовать все примеры из лекции. Привести текст примера и текст результата, например:
+
+># Конспект лекции "Исключения"
+>
+>## Деление на `0`
+>
+>```cs
+>int x = 5;
+>int y = x / 0;
+>Console.WriteLine($"Результат: {y}");
+>Console.WriteLine("Конец программы");
+>Console.Read();
+>```
+>
+>```
+>Unhandled exception. System.DivideByZeroException: >Attempted to divide by zero.
+>   at Program.<Main>$(String[] args) in /home/kei/>RiderProjects/tryCatch/Program.cs:line 2
+>
+>Process finished with exit code 134.
+>```
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Делегаты, события и лямбды](./t5_delegate.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [Многопоточность. Потоки, асинхронные вычисления](./t5_thread_async.md)

+ 1225 - 0
articles/t5_file_types.md

@@ -0,0 +1,1225 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Работа с файловой системой и файлами.](./t5_files.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [Регулярные выражения](./t5_regex.md)
+
+>**Задание на дом:**
+>
+>XML делать не надо, только CSV и JSON. Но для JSON реализовать и сереализацию и десереализацию.
+
+# Форматы файлов
+
+* [CSV](#CSV)
+* [XML](#XML)
+* [JSON](#JSON)
+
+## CSV
+
+CSV (от англ. Comma-Separated Values — значения, разделённые запятыми) — текстовый формат, предназначенный для представления табличных данных. Строка таблицы соответствует строке текста, которая содержит одно или несколько полей, разделенных запятыми.
+
+Формат CSV стандартизирован не полностью. Идея использовать запятые для разделения полей очевидна, но при таком подходе возникают проблемы, если исходные табличные данные содержат запятые или переводы строк. Возможным решением проблемы запятых и переносов строк является заключение данных в кавычки, однако исходные данные могут содержать кавычки. Помимо этого термином «CSV» могут обозначаться похожие форматы, в которых разделителем является символ табуляции (TSV) или точка с запятой. Многие приложения, которые работают с форматом CSV, позволяют выбирать символ разделителя и символ кавычек.
+
+**Спецификация**
+
+* Каждая строка файла — это одна строка таблицы.
+* Разделителем (англ. delimiter) значений колонок является символ запятой (,). Однако на практике часто используются другие разделители, то есть формат путают с DSVruen и TSV (см. ниже).
+* Значения, содержащие зарезервированные символы (двойная кавычка, запятая, точка с запятой, новая строка) обрамляются двойными кавычками ("). Если в значении встречаются кавычки — они представляются в файле в виде двух кавычек подряд.
+
+В библиотеке C# нет методов работы с CSV, но можно использовать сторонние библиотеки.
+
+Для загрузки сторонних библиотек есть менеджер NuGet пакетов. Для его открытия в контекстном меню проекта (клик правой кнопкой мыши по названию проекта) выберите пункт Manage NuGet Packeges:
+
+![](../img/rider_nuget.png)
+
+В нижней части экрана появится окно NuGet
+
+![](../img/rider_csvhelper.png)
+
+В закладке Packages в строке поиска введите CsvHelper и добавьте найденный пакет в свой проект.
+
+>Можно сделать проще: выполнить команду `dotnet add package CsvHelper` в каталоге проекта, но для этого надо знать название пакета.
+
+### Чтение данных из существующего файла
+
+Прежде чем читать данные, надо создать файл.
+
+1. Переключите режим просмотра структуры проекта в "File System":
+
+    "Текущим" каталогом при выполнении программы будет тот, где находится скомпилированная программа. В моём случае это `bin/Debug/net8.0` (зависит о режима сборки и версии .NET)
+
+    ![](../img/rider_testcsv.png)
+
+    >Если каталога bin с подкаталогами ещё нет, но нужно "собрать" (build) программу
+
+1. Создайте в каталоге с программой файл `test.csv` c таким содержимым:
+
+    ```csv
+    description,value
+    "строка с пробелами",123.15
+    "строка
+    с переносом 
+    строки",456
+    ```
+
+    В первой строке заголовки столбцов. Далее ДВЕ строки с данными. Обратите внимание: для проверки правильности разбора сложных строк я добавил литералы с переносами строк
+
+Теперь можно писать код:
+
+```cs
+using System.Globalization;
+using CsvHelper;
+using CsvHelper.Configuration;
+
+using (var reader = new StreamReader("./test.csv")) {
+    using (var csv = new CsvReader(
+        reader, CultureInfo.InvariantCulture))
+    {
+        var records = csv.GetRecords<Foo>();
+        foreach (var record in records) {
+            Console.WriteLine("{0}, {1}", record.description, record.value);
+        }
+    }
+}
+
+public class Foo
+{
+    public string description { get; set; }
+    public double value { get; set; }
+}
+```
+
+* Сначала считывается содержимое файла обычным **StreamReader** (путь `./` означает, что чтение из текущего каталога)
+* Затем содержимое отдаётся классу **CsvReader**
+* При чтении данных (_GetRecords_) используются класс **Foo** (просто как описание структуры). Классы мы пока не проходили, но тут пока ничего сложного
+
+    В классе у нас определены поля, соответствующие колонкам в нашем файле
+
+* В цикле перебираем записи (строки) нашего файла и выводим содержимое в консоль.
+
+Это вариант по-умолчанию, в котором обязательны заголовки колонок и разделитель запятая. Если у нас данные без заголовков или с другими разделителями, то можно задать конфигурацию:
+
+```cs
+...
+
+var config = new CsvConfiguration(
+    CultureInfo.InvariantCulture) { 
+        Delimiter = ";", 
+        HasHeaderRecord = false };
+
+using (var reader = new StreamReader("./test2.csv")) {
+    using (var csv = new CsvReader(reader, config))
+
+...
+```
+
+### Запись в CSV
+
+```cs
+// использую не стандартный формат
+var config = new CsvConfiguration(
+    CultureInfo.InvariantCulture) { 
+        Delimiter = ";", 
+        HasHeaderRecord = false };
+
+// создаю тестовые данные
+var records = new List<Foo>
+{
+    new Foo { 
+        description ="тест записи\nмногострочного текста",
+        value = 12.34 },
+    new Foo { 
+        description = "просто, текст, с запятыми", 
+        value = 0 }
+};
+
+using (var writer = new StreamWriter("./test3.csv"))
+using (var csv = new CsvWriter(writer, config))
+{
+    csv.WriteRecords(records);
+}
+```
+
+Получится такой файл:
+
+```csv
+"тест записи
+многострочного текста";12.34
+просто, текст, с запятыми;0
+```
+
+### Различия в культурной среде
+
+В русской культурной среде в качестве разделителя разрядов в числах с плавающей запятой используется запятая. Поэтому вполне может попасться csv файл, в котором в числах разделитель запятая. 
+
+```csv
+"строка с пробелами";123,15
+"строка
+с переносом 
+строки";456
+```
+
+Разделитель полей мы менять умеем, но как быть с форматом чисел?
+
+Нужно задать нужную культурную среду:
+
+```cs
+var config = new CsvConfiguration(
+    new CultureInfo("ru-RU")) { 
+        Delimiter = ";", 
+        HasHeaderRecord = false };
+```
+
+## XML
+
+<!-- https://habr.com/ru/post/524288/ -->
+
+XML, в переводе с англ *eXtensible Markup Language* — расширяемый язык разметки. Используется для хранения и передачи данных. Так что увидеть его можно не только в API, но и в коде.
+
+>Как формат передачи данных он устарел, сейчас используется JSON.
+
+### Как устроен XML
+
+Возьмем пример:
+
+```xml
+<req>
+    <query>Виктор Иван</query>
+    <count>7</count>
+</req>
+```
+
+И разберёмся, что означает эта запись.
+
+#### Теги
+
+В XML каждый элемент должен быть заключен в теги. Тег — это некий текст, обернутый в угловые скобки:
+
+```xml
+<tag>
+```
+
+Текст внутри угловых скобок — название тега.
+
+Тега всегда два:
+
+* Открывающий — текст внутри угловых скобок `<tag>`
+* Закрывающий — тот же текст (это важно!), но добавляется символ «/»: `</tag>`
+
+Бывают еще пустые элементы, у них один тег и открывающий, и закрывающий одновременно. Но об этом чуть позже!
+
+С помощью тегов мы показываем системе «вот тут начинается элемент, а вот тут заканчивается». Это как дорожные знаки:
+
+* На въезде в город написано его название: **Москва**
+* На выезде написано то же самое название, но перечеркнутое: **~~Москва~~**
+
+#### Корневой элемент
+
+В любом XML-документе есть корневой элемент. Это тег, с которого документ начинается, и которым заканчивается.
+
+В нашем примере корневой элемент — `<req>`.
+
+Он мог бы называться по другому:
+
+```xml
+<main>
+<sugg>
+```
+
+Да как угодно. Он показывает начало и конец нашего документа, не более того. А вот внутри уже идет тело документа. Те параметры, которые мы передаем внешней системе. Разумеется, они тоже будут в тегах, но уже в обычных, а не корневых.
+
+#### Значение элемента
+
+Значение элемента хранится между открывающим и закрывающим тегами. Это может быть число, строка, или даже вложенные теги!
+
+```xml
+<req>
+    <query>Виктор Иван</query>
+    ^^^^^^^           ^^^^^^^^ вложенный тег
+    <count>7</count>
+    ^^^^^^^ ^^^^^^^^ аналогично
+</req>
+```
+
+Внутри тега — значение:
+
+```xml
+<query>Виктор Иван</query>
+       ^^^^^^^^^^^ значение
+```
+
+Обратите внимание:
+
+* Виктор Иван — строка
+* 7 — число
+
+Но оба значения идут без кавычек. В XML нам нет нужды брать строковое значение в кавычки.
+
+#### Атрибуты элемента
+
+У элемента могут быть атрибуты — один или несколько. Их мы указываем внутри отрывающегося тега после названия тега через пробел в виде
+
+```
+название_атрибута = "значение атрибута"
+```
+
+Например:
+
+```xml
+<query attr1=“value 1”>
+    Виктор Иван
+</query>
+<query attr1=“value 1” attr2=“value 2”>
+    Виктор Иван
+</query>
+```
+
+Зачем это нужно? Из атрибутов принимающая API-запрос система понимает, что такое ей вообще пришло.
+
+Например, мы делаем поиск по системе, ищем клиентов с именем Олег. Отправляем простой запрос:
+
+```xml
+<query>Олег</query>
+```
+
+А в ответ получаем целую пачку Олегов! С разными датами рождения, номерами телефонов и другими данными. Допустим, что один из результатов поиска выглядит так:
+
+```xml
+<party type="PHYSICAL" sourceSystem="AL" rawId="2">
+    <field name="name">
+        Олег
+    </field>
+    <field name="birthdate">
+        02.01.1980
+    </field>
+    <attribute type="PHONE" rawId="AL.2.PH.1">
+        <field name="type">
+            MOBILE
+        </field>
+        <field name="number">
+            +7 916 1234567
+        </field>
+    </attribute>
+</party>
+```
+
+Давайте разберем эту запись. У нас есть основной элемент **party**.
+
+У него есть 3 атрибута:
+
+* `type = "PHYSICAL"` — тип возвращаемых данных. Нужен, если система умеет работать с разными типами: ФЛ, ЮЛ, ИП. Тогда благодаря этому атрибуту мы понимаем, с чем именно имеем дело и какие поля у нас будут внутри. А они будут отличаться! У физика это может быть ФИО, дата рождения ИНН, а у юр лица — название компании, ОГРН и КПП
+* `sourceSystem = "AL"` — исходная система. Возможно, нас интересуют только физ лица из одной системы, будем делать отсев по этому атрибуту.
+* `rawId = "2"` — идентификатор в исходной системе. Он нужен, если мы шлем запрос на обновление клиента, а не на поиск. Как понять, кого обновлять? По связке sourceSystem + rawId!
+
+Внутри party есть элементы **field**.
+
+У элементов **field** есть атрибут *name*. Значение атрибута — название поля: имя, дата рождения, тип или номер телефона. Так мы понимаем, что скрывается под конкретным **field**.
+
+Это удобно с точки зрения поддержки, когда у вас коробочный продукт и 10+ заказчиков. У каждого заказчика будет свой набор полей: у кого-то в системе есть ИНН, у кого-то нету, одному важна дата рождения, другому нет, и т.д.
+
+Но, несмотря на разницу моделей, у всех заказчиков будет одна XSD-схема (которая описывает запрос и ответ):
+
+* есть элемент **party**;
+* у него есть элементы **field**;
+* у каждого элемента **field** есть атрибут *name*, в котором хранится название поля.
+
+А вот конкретные названия полей уже можно не описывать в XSD. Их уже *смотрите в ТЗ*. Конечно, когда заказчик один или вы делаете ПО для себя или *вообще для всех*, удобнее использовать именованные поля — то есть *говорящие* теги. Какие плюшки у этого подхода:
+
+* При чтении XSD сразу видны реальные поля. ТЗ может устареть, а код будет актуален.
+* Запрос легко дернуть вручную в SOAP Ui — он сразу создаст все нужные поля, нужно только значениями заполнить. Это удобно тестировщику + заказчик иногда так тестирует, ему тоже хорошо.
+
+В общем, любой подход имеет право на существование. Надо смотреть по проекту, что будет удобнее именно вам. У меня в примере неговорящие названия элементов — все как один будут **field**. А вот по атрибутам уже можно понять, что это такое.
+
+Помимо элементов **field** в **party** есть элемент **attribute**. Не путайте xml-нотацию и бизнес-прочтение:
+
+* с точки зрения бизнеса это атрибут физ лица, отсюда и название элемента — **attribute**.
+* с точки зрения xml — это **элемент** (не атрибут!), просто его назвали **attribute**. XML все равно (почти), как вы будете называть элементы, так что это допустимо.
+
+У **элемента** **attribute** есть *атрибуты*:
+
+* `type = "PHONE"` — тип атрибута. Они ведь разные могут быть: телефон, адрес, емейл...
+* `rawId = "AL.2.PH.1"` — идентификатор в исходной системе. Он нужен для обновления. Ведь у одного клиента может быть несколько телефонов, как без ID понять, какой именно обновляется?
+
+акая вот XML-ка получилась. Причем упрощенная. В реальных системах, где хранятся физ лица, данных сильно больше: штук 20 полей самого физ лица, несколько адресов, телефонов, емейл-адресов…
+
+Но прочитать даже огромную XML не составит труда, если вы знаете, что где. И если она отформатирована — вложенные элементы сдвинуты вправо, остальные на одном уровне. Без форматирования будет тяжеловато…
+
+А так всё просто — у нас есть элементы, заключенные в теги. Внутри тегов — название элемента. Если после названия идет что-то через пробел: это атрибуты элемента.
+
+
+#### XML пролог
+
+Иногда вверху XML документа можно увидеть что-то похожее:
+
+`<?xml version="1.0" encoding="UTF-8"?>`
+
+Эта строка называется XML прологом. Она показывает версию XML, который используется в документе, а также кодировку. Пролог необязателен, если его нет — это ок. Но если он есть, то это должна быть первая строка XML документа.
+
+UTF-8 — кодировка XML документов по умолчанию.
+
+### Работа с XML в C#
+
+Прежде чем перейти непосредственно к работе с XML-файлами, сначала рассмотрим, что представляет собой xml-документ и как он может хранить объекты, используемые в программе на c#.
+
+Например, у нас есть следующий класс:
+
+```cs
+class User
+{
+    public string Name { get; set; }
+    public int Age { get; set; }
+    public string Company { get; set; }
+}
+```
+
+В программе на C# мы можем создать список объектов класса User:
+
+```cs
+User user1 = new User { Name = "Bill Gates", Age = 48, Company = "Microsoft" };
+User user2 = new User { Name = "Larry Page", Age = 42, Company = "Google" };
+List<User> users = new List<User> { user1, user2 };
+```
+
+Чтобы сохранить список в формате xml мы могли бы использовать следующий xml-файл:
+
+```xml
+<?xml version="1.0" encoding="utf-8" ?>
+<users>
+  <user name="Bill Gates">
+    <company>Microsoft</company>
+    <age>48</age>
+  </user>
+  <user name="Larry Page">
+    <company>Google</company>
+    <age>48</age>
+  </user>
+</users>
+```
+
+XML-документ должен иметь один единственный корневой элемент, внутрь которого помещаются все остальные элементы. В данном случае таким элементом является элемент `<users>`. Внутри корневого элемента `<users>` задан набор элементов `<user>`. Вне корневого элемента мы не можем разместить элементы user.
+
+Элемент может иметь вложенные элементы и атрибуты. В данном случае каждый элемент **user** имеет два вложенных элемента **company** и **age** и атрибут *name*.
+
+Внутри простых элементов помещается их значение. Например, `<company>Google</company>` - элемент company имеет значение Google.
+
+Названия элементов являются регистрозависимыми, поэтому `<company>` и `<COMPANY>` будут представлять разные элементы.
+
+Таким образом, весь список Users из кода C# сопоставляется с корневым элементом `<users>`, каждый объект User - с элементом `<user>`, а каждое свойство объекта User - с атрибутом или вложенным элементом элемента `<user>`
+
+Что использовать для свойств - вложенные элементы или атрибуты? Это вопрос предпочтений - мы можем использовать как атрибуты, так и вложенные элементы. Так, в предыдущем примере вполне можно использовать вместо атрибута вложенный элемент:
+
+```xml
+<?xml version="1.0" encoding="utf-8" ?>
+<users>
+  <user>
+    <name>Bill Gates</name>
+    <company>Microsoft</company>
+    <age>48</age>
+  </user>
+  <user>
+    <name>Larry Page</name>
+    <company>Google</company>
+    <age>48</age>
+  </user>
+</users>
+```
+
+Теперь рассмотрим основные подходы для работы с XML, которые имеются в C#.
+
+#### LINQ to XML
+
+Для работы с XML в C# можно использовать несколько подходов. В первых версиях фреймворка основной функционал работы с XML предоставляло пространство имен **System.Xml**. 
+
+Еще один подход к работе с Xml представляет технология **LINQ to XML**. Вся функциональность LINQ to XML содержится в пространстве имен System.Xml.Linq. Рассмотрим основные классы этого пространства имен:
+
+* **XAttribute**: представляет атрибут xml-элемента
+* **XComment**: представляет комментарий
+* **XDocument**: представляет весь xml-документ
+* **XElement**: представляет отдельный xml-элемент
+
+Ключевым классом является **XElement**, который позволяет получать вложенные элементы и управлять ими. Среди его методов можно отметить следующие:
+
+* **Add()**: добавляет новый атрибут или элемент
+* **Attributes()**: возвращает коллекцию атрибутов для данного элемента
+* **Elements()**: возвращает все дочерние элементы данного элемента
+* **Remove()**: удаляет данный элемент из родительского объекта
+* **RemoveAll()**: удаляет все дочерние элементы и атрибуты у данного элемента
+
+Итак, используем функциональность LINQ to XML и создадим новый XML-документ:
+
+```cs
+XDocument xdoc = new XDocument();
+// создаем первый элемент
+XElement iphone6 = new XElement("phone");
+// создаем атрибут
+XAttribute iphoneNameAttr = new XAttribute("name", "iPhone 6");
+XElement iphoneCompanyElem = new XElement("company", "Apple");
+XElement iphonePriceElem = new XElement("price", "40000");
+// добавляем атрибут и элементы в первый элемент
+iphone6.Add(iphoneNameAttr);
+iphone6.Add(iphoneCompanyElem);
+iphone6.Add(iphonePriceElem);
+ 
+// создаем второй элемент
+XElement galaxys5 = new XElement("phone");
+XAttribute galaxysNameAttr = new XAttribute("name", "Samsung Galaxy S5");
+XElement galaxysCompanyElem = new XElement("company", "Samsung");
+XElement galaxysPriceElem = new XElement("price", "33000");
+galaxys5.Add(galaxysNameAttr);
+galaxys5.Add(galaxysCompanyElem);
+galaxys5.Add(galaxysPriceElem);
+
+// создаем корневой элемент
+XElement phones = new XElement("phones");
+// добавляем в корневой элемент
+phones.Add(iphone6);
+phones.Add(galaxys5);
+// добавляем корневой элемент в документ
+xdoc.Add(phones);
+//сохраняем документ
+xdoc.Save("phones.xml");
+```
+
+Чтобы создать документ, нам нужно создать объект класса **XDocument**. Это объект самого верхнего уровня в хml-документе.
+
+Элементы создаются с помощью конструктора класса **XElement**. Конструктор имеет ряд перегруженных версий. Первый параметр конструктора передает название элемента, например, phone. Второй параметр передает значение этого элемента.
+
+Создание атрибута аналогично созданию элемента. Затем все атрибуты и элементы добавляются в элементы phone с помощью метода Add().
+
+Так как документ xml должен иметь один корневой элемент, то затем все элементы phone добавляются в один контейнер - элемент phones.
+
+В конце корневой элемент добавляется в объект XDocument, и этот объект сохраняется на диске в xml-файл с помощью метода Save().
+
+Если мы откроем сохраненный файл `phones.xml`, то увидим в нем следующее содержание:
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<phones>
+  <phone name="iPhone 6">
+    <company>Apple</company>
+    <price>40000</price>
+  </phone>
+  <phone name="Samsung Galaxy S5">
+    <company>Samsung</company>
+    <price>33000</price>
+  </phone>
+</phones>
+```
+
+Конструктор класса XElement позволяют задать набор объектов, которые будут входить в элемент. И предыдущий пример мы могли бы сократить следующим способом:
+
+```cs
+XDocument xdoc = new XDocument(new XElement("phones",
+    new XElement("phone",
+        new XAttribute("name", "iPhone 6"),
+        new XElement("company", "Apple"),
+        new XElement("price", "40000")),
+    new XElement("phone",
+        new XAttribute("name", "Samsung Galaxy S5"),
+        new XElement("company", "Samsung"),
+        new XElement("price", "33000"))));
+xdoc.Save("phones.xml");
+```
+
+#### Выборка элементов в LINQ to XML
+
+Возьмем xml-файл, созданный в прошлой теме:
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<phones>
+  <phone name="iPhone 6">
+    <company>Apple</company>
+    <price>40000</price>
+  </phone>
+  <phone name="Samsung Galaxy S5">
+    <company>Samsung</company>
+    <price>33000</price>
+  </phone>
+</phones>
+```
+
+Переберем его элементы и выведем их значения на консоль:
+
+```cs
+XDocument xdoc = XDocument.Load("phones.xml");
+foreach (XElement phoneElement in xdoc.Element("phones").Elements("phone"))
+{
+    XAttribute nameAttribute = phoneElement.Attribute("name");
+    XElement companyElement = phoneElement.Element("company");
+    XElement priceElement = phoneElement.Element("price");
+     
+    if (nameAttribute != null && companyElement!=null && priceElement!=null)
+    {
+        Console.WriteLine($"Смартфон: {nameAttribute.Value}");
+        Console.WriteLine($"Компания: {companyElement.Value}");
+        Console.WriteLine($"Цена: {priceElement.Value}");
+    }
+    Console.WriteLine();
+}
+```
+
+И мы получим следующий вывод:
+
+```
+Смартфон: iPhone 6
+Компания: Apple
+Цена: 40000
+
+Смартфон: Samsung Galaxy S5
+Компания: Samsung
+Цена: 33000
+```
+
+Чтобы начать работу с имеющимся xml-файлом, надо сначала загрузить его с помощью статического метода *XDocument.Load()*, в который передается путь к файлу.
+
+Поскольку xml хранит иерархически выстроенные элементы, то и для доступа к элементам надо идти начиная с высшего уровня в этой иерархии и далее вниз. Так, для получения элементов phone и доступа к ним надо сначала обратиться к корневому элементу, а через него уже к элементам phone: *xdoc.Element("phones").Elements("phone")*
+
+Метод *Element("имя_элемента")* возвращает первый найденный элемент с таким именем. Метод *Elements("имя_элемента")* возвращает коллекцию одноименных элементов. В данном случае мы получаем коллекцию элементов phone и поэтому можем перебрать ее в цикле.
+
+Спускаясь дальше по иерархии вниз, мы можем получить атрибуты или вложенные элементы, например, `XElement companyElement = phoneElement.Element("company")`
+
+Значение простых элементов, которые содержат один текст, можно получить с помощью свойства Value: `string company = phoneElement.Element("company").Value`
+
+Сочетая операторы Linq и LINQ to XML можно довольно просто извлечь из документа данные и затем обработать их. Например, имеется следующий класс:
+
+```cs
+class Phone
+{
+    public string Name { get; set; }
+    public string Price { get; set; }
+}
+```
+
+Создадим на основании данных в xml объекты этого класса:
+
+```cs
+XDocument xdoc = XDocument.Load("phones.xml");
+var items = from xe in xdoc.Element("phones").Elements("phone")
+            where xe.Element("company").Value=="Samsung"
+            select new Phone 
+            { 
+                Name = xe.Attribute("name").Value, 
+                Price = xe.Element("price").Value 
+            };
+ 
+foreach (var item in items)
+    Console.WriteLine($"{item.Name} - {item.Price}");
+```
+
+#### Изменение документа в LINQ to XML
+
+Возьмем xml-файл из прошлой темы:
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<phones>
+  <phone name="iPhone 6">
+    <company>Apple</company>
+    <price>40000</price>
+  </phone>
+  <phone name="Samsung Galaxy S5">
+    <company>Samsung</company>
+    <price>33000</price>
+  </phone>
+</phones>
+```
+
+И отредактируем его содержимое:
+
+```cs
+using System;
+using System.Xml.Linq;
+using System.Linq;
+ 
+namespace HelloApp
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            XDocument xdoc = XDocument.Load("phones.xml");
+            XElement root = xdoc.Element("phones");
+ 
+            foreach (XElement xe in root.Elements("phone").ToList())
+            {
+                // изменяем название и цену
+                if (xe.Attribute("name").Value == "Samsung Galaxy S5")
+                {
+                    xe.Attribute("name").Value = "Samsung Galaxy Note 4";
+                    xe.Element("price").Value = "31000";
+                }
+                //если iphone - удаляем его
+                else if (xe.Attribute("name").Value == "iPhone 6")
+                {
+                    xe.Remove();
+                }
+            }
+            // добавляем новый элемент
+            root.Add(new XElement("phone",
+                        new XAttribute("name", "Nokia Lumia 930"),
+                        new XElement("company", "Nokia"),
+                        new XElement("price", "19500")));
+            xdoc.Save("pnones1.xml");
+            // выводим xml-документ на консоль
+            Console.WriteLine(xdoc);
+ 
+            Console.Read();
+        }
+    }
+}
+```
+
+Для изменения содержимого простых элементов и атрибутов достаточно изменить их свойство Value: `xe.Element("price").Value = "31000"`
+
+Если же нам надо редактировать сложный элемент, то мы можем использовать комбинацию методов Add/Remove для добавления и удаления вложенных элементов.
+
+В результате сформируется и сохранится на диск новый документ:
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<phones>
+  <phone name="Samsung Galaxy Note 4">
+    <company>Samsung</company>
+    <price>31000</price>
+  </phone>
+  <phone name="Nokia Lumia 930">
+    <company>Nokia</company>
+    <price>19500</price>
+  </phone>
+</phones>
+```
+
+#### Сериализация в XML. XmlSerializer
+
+Для удобного сохранения и извлечения объектов из файлов xml может использоваться класс **XmlSerializer**.
+
+**Во-первых**, **XmlSerializer** предполагает некоторые ограничения. Например, класс, подлежащий сериализации, должен иметь стандартный конструктор без параметров. Также сериализации подлежат только открытые члены. Если в классе есть поля или свойства с модификатором private, то при сериализации они будут игнорироваться.
+
+**Во-вторых**, **XmlSerializer** требует указания типа:
+
+```cs
+using System;
+using System.IO;
+using System.Xml.Serialization;
+ 
+namespace Serialization
+{
+    // класс и его члены объявлены как public
+    [Serializable]
+    public class Person
+    {
+        public string Name { get; set; }
+        public int Age { get; set; }
+ 
+        // стандартный конструктор без параметров
+        public Person()
+        { }
+ 
+        public Person(string name, int age)
+        {
+            Name = name;
+            Age = age;
+        }
+    }
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            // объект для сериализации
+            Person person = new Person("Tom", 29);
+            Console.WriteLine("Объект создан");
+ 
+            // передаем в конструктор тип класса
+            XmlSerializer formatter = new XmlSerializer(typeof(Person));
+ 
+            // получаем поток, куда будем записывать сериализованный объект
+            using (FileStream fs = new FileStream("persons.xml", FileMode.OpenOrCreate))
+            {
+               formatter.Serialize(fs, person);
+ 
+                Console.WriteLine("Объект сериализован");
+            }
+ 
+            // десериализация
+            using (FileStream fs = new FileStream("persons.xml", FileMode.OpenOrCreate))
+            {
+                Person newPerson = (Person)formatter.Deserialize(fs);
+ 
+                Console.WriteLine("Объект десериализован");
+                Console.WriteLine($"Имя: {newPerson.Name} --- Возраст: {newPerson.Age}");
+            }
+ 
+            Console.ReadLine();
+        }
+    }
+}
+```
+
+Итак, класс **Person** **публичный** и имеет публичные свойства, поэтому он может сериализоваться. При создании объекта XmlSerializer передаем в конструктор тип класса. Метод Serialize добавляет данные в файл `persons.xml`. А метод Deserialize извлекает их оттуда.
+
+Если мы откроем `файл persons.xml`, то увидим содержание нашего объекта:
+
+```xml
+<?xml version="1.0"?>
+<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+  <Name>Tom</Name>
+  <Age>29</Age>
+</Person>
+```
+
+Таким же образом мы можем сериализовать массив или коллекцию объектов, но главное требование состоит в том, чтобы в них был определен стандартный конструктор:
+
+```cs
+Person person1 = new Person("Tom", 29);
+Person person2 = new Person("Bill", 25);
+Person[] people = new Person[] { person1, person2 };
+ 
+XmlSerializer formatter = new XmlSerializer(typeof(Person[]));
+ 
+using (FileStream fs = new FileStream("people.xml", FileMode.OpenOrCreate))
+{
+    formatter.Serialize(fs, people);
+}
+ 
+using (FileStream fs = new FileStream("people.xml", FileMode.OpenOrCreate))
+{
+    Person[] newpeople = (Person[])formatter.Deserialize(fs);
+ 
+    foreach (Person p in newpeople)
+    {
+        Console.WriteLine($"Имя: {p.Name} --- Возраст: {p.Age}");
+    }
+}
+```
+
+Но это был простой объект. Однако с более сложными по составу объектами работать так же просто. Например:
+
+```cs
+using System;
+using System.IO;
+using System.Xml.Serialization;
+ 
+namespace Serialization
+{
+    [Serializable]
+    public class Person
+    {
+        public string Name { get; set; }
+        public int Age { get; set; }
+        public Company Company { get; set; }
+ 
+        public Person()
+        { }
+ 
+        public Person(string name, int age, Company comp)
+        {
+            Name = name;
+            Age = age;
+            Company = comp;
+        }
+    }
+ 
+    [Serializable]
+    public class Company
+    {
+        public string Name { get; set; }
+ 
+        // стандартный конструктор без параметров
+        public Company() { }
+ 
+        public Company(string name)
+        {
+            Name = name;
+        }
+    }
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            Person person1 = new Person("Tom", 29, new Company("Microsoft"));
+            Person person2 = new Person("Bill", 25, new Company("Apple"));
+            Person[] people = new Person[] { person1, person2 };
+ 
+            XmlSerializer formatter = new XmlSerializer(typeof(Person[]));
+ 
+            using (FileStream fs = new FileStream("people.xml", FileMode.OpenOrCreate))
+            {
+               formatter.Serialize(fs, people);
+            }
+ 
+            using (FileStream fs = new FileStream("people.xml", FileMode.OpenOrCreate))
+            {
+                Person[] newpeople = (Person[])formatter.Deserialize(fs);
+ 
+                foreach (Person p in newpeople)
+                {
+                    Console.WriteLine($"Имя: {p.Name} --- Возраст: {p.Age} --- Компания: {p.Company.Name}");
+                }
+            }
+            Console.ReadLine();
+        }
+    }
+}
+```
+
+Класс Person содержит свойство Company, которое будет хранить объект класса Company. Члены класса Company объявляются с модификатором public, кроме того также присутствует стандартный конструктор без параметров. В итоге после сериализации мы получим следующий xml-документ:
+
+```cs
+<?xml version="1.0"?>
+<ArrayOfPerson xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+  <Person>
+    <Name>Tom</Name>
+    <Age>29</Age>
+    <Company>
+      <Name>Microsoft</Name>
+    </Company>
+  </Person>
+  <Person>
+    <Name>Bill</Name>
+    <Age>25</Age>
+    <Company>
+      <Name>Apple</Name>
+    </Company>
+  </Person>
+</ArrayOfPerson>
+```
+
+#### Замечания по сериализации массивов
+
+>Подробнее можно посмотреть [тут](https://docs.microsoft.com/ru-ru/dotnet/standard/serialization/controlling-xml-serialization-using-attributes)
+
+Допустим в классе есть свойство типа *массив строк*:
+
+```cs
+public string[] SomeField {get; set;}
+```
+
+При сериализации такой массив преобразуется в такой XML:
+
+```xml
+<string>value1</string>
+<string>value1</string>
+<string>value1</string>
+<string>value1</string>
+```
+
+Т.е. названиями тегов элемента массива будет тип данных (string). При сериализации это не принципиально, но если мы принимаем какие-то данные из внешнего источника, то названия тегов могут быть произвольными и не соответствующие каким-то типам данных C#, например:
+
+```xml
+<item>1</item>
+<item>2</item>
+<item>3</item>
+<item>4</item>
+```
+
+Для десериализации (и сериализации) такого массива нужно добавить перед полем атрибут **XmlArrayItem**, в параметрах которого указать требуемое название тега:
+
+```cs
+[XmlArrayItem("item")]
+public int[] SomeIntArray;
+```
+
+Если нужно поменять и название массива, то используйте атрибут **XmlArray**:
+
+```cs
+[XmlArray("ArrayOfInteger")]
+[XmlArrayItem("item")]
+public int[] SomeIntArray;
+```
+
+При сериализации получим такую иерархию тегов:
+
+```xml
+<ArrayOfInteger>
+    <item>1</item>
+    <item>2</item>
+    <item>3</item>
+    <item>4</item>
+</ArrayOfInteger>
+```
+
+
+## JSON
+
+JSON (JavaScript Object Notation). Можно перевести как *способ записи объектов в JavaScript*. Формат оказался настолько удобен, что его стали поддерживать практически все популярные языки программирования.
+
+### Как устроен этот формат
+
+Допустим, у нас есть магазин с системой бонусов, которые начисляются по скидочной карте. Когда продавец считывает карту, он должен получить от сервера такие данные:
+
+* имя,
+* фамилию,
+* телефон,
+* город,
+* возраст,
+* количество бонусных баллов,
+* три предыдущие покупки (чтобы порекомендовать к ним что-то подходящее).
+
+А теперь посмотрите на JSON-ответ, который получит продавец после считывания карты:
+
+```json
+{
+  "firstname": "Михаил",
+  "lastname": "Максимов",
+  "phone": "+79201234567",
+  "city": "Москва",
+  "age": 37,
+  "bonus": 2000,
+  "prev": [
+    "Кроссовки",
+    "Турник",
+    "Зимняя куртка"
+  ]
+}
+```
+
+Общее правило такое: сначала всегда идёт название какого-то поля, а через двоеточие — его значение. Названия всегда берутся в двойные кавычки, строковые значения — тоже.
+
+Ещё есть такое:
+
+* вложенные объекты берутся в фигурные скобки;
+* массивы берутся в прямоугольные скобки;
+* после каждой пары «свойство: значение» должна стоять запятая (в самом конце — не ставится).
+
+Так как JSON — универсальный формат передачи данных, то он может работать только с теми данными, которые есть в большинстве языков:
+
+* **строки** — тоже, как и названия, берутся в двойные кавычки;
+числа, можно дробные;
+* **логические значения** `true` или `false`; 
+* **массивы** или **объекты**.
+
+То, что не входит в этот список, JSON не обработает и не выдаст сообщение об ошибке, потому что JSON — это просто формат данных и за его правильностью должен следить программист.
+
+### Newtonsoft.Json
+
+Эта библиотека является стандартом де-факто для работы с JSON в C#.
+
+Пример **сериализации** (преобразование объекта в JSON-строку)
+
+```cs
+var person = new Person
+{
+    name = "Имя",
+    age = 18,
+    date = "2024-03-07"
+};
+
+string json = JsonConvert.SerializeObject(
+    person, Formatting.Indented);
+
+Console.WriteLine(json);
+```
+
+Должны получить что-то подобное:
+
+```json
+{
+    "name": "Имя",
+    "age":18,
+    "date":"2024-03-07"
+}
+```
+
+### Работа с JSON. DataContractJsonSerializer.
+
+>Этот раздел устаревший (написан под .net framework и не факт, что заведётся в .net core). Лучше использовать библиотеку **Newtonsoft.Json**
+
+Для начала надо подключить пространство имен *System.Runtime.Serialization*
+
+![](../img/05004.png)
+
+
+Во-вторых, добавить атрибуты сериализуемому классу:
+
+```cs
+[DataContract]
+internal class Person
+{
+    [DataMember]
+    public string name;
+
+    [DataMember]
+    public int age;
+
+    [DataMember]
+    public DateTime date;
+}
+```
+
+Для того чтобы сериализация объекта стала возможна он должен быть отмечен атрибутом **DataContract**, а его члены подлежащие сериализации атрибутом **DataMember** (причем таким атрибутом может быть помечено даже приватное свойство).
+
+**Сериализация** (Преобразование объекта в JSON-строку)
+
+А дальше просто пишем в файл (или в любой другой поток).
+
+```cs
+var PersonList = new List<Person>() {
+    new Person {name="Иванов", age=25, date=new DateTime(2021,1,1)},
+    new Person {name="Петров", age=35, date=new DateTime(2021,1,2)}
+};
+
+// создаем объект сериализатора, указав, что на входе 
+// будет МАССИВ объектов Person
+var Serializer = new DataContractJsonSerializer(typeof(Person[]));
+
+using (var streamWriter = new StreamWriter("test1.json"))
+{
+    Serializer.WriteObject(
+        streamWriter.BaseStream,
+        // Преобразуем список (List) объектов в МАССИВ
+        PersonList.ToArray()
+    );
+}
+```
+
+На выходе получим следующее:
+
+```json
+[{"age":25,"date":"\/Date(1609448400000+0300)\/","name":"Иванов"},{"age":35,"date":"\/Date(1609534800000+0300)\/","name":"Петров"}]
+```
+
+*Замечания по коду:*
+
+Первым параметром метод *Serializer.WriteObject* ждет поток данных (Stream). Передавать ему переменную *streamWriter* нельзя, т.к. у неё другой тип (StreamWriter). 
+
+В инете почему-то для получения потока из файла используют многоступенчатую схему:
+* сначала читают содержимое файла в объект типа MemoryStream
+* затем передают этот объект в сериализатор
+
+Но при первом же вызове *InteliSense* видно, что у StreamWriter-а есть публичное свойство *BaseStream*, которое вполне можно использовать в сериализаторе.
+
+>Двухступенчатая схема имеет смысл, если исходные данные не в UNICODE, когда исходный поток нужно преобразовать в другую кодировку.
+
+```cs
+var PersonList = (Person[])Serializer.ReadObject(
+    new MemoryStream(
+        Encoding.UTF8.GetBytes(
+            "[{\"name\":\"Иванов\",\"age\":20},{\"name\":\"Петров\",\"age\":20}]"
+        )
+    )
+);
+```            
+
+**Десериализация** (Преобразование JSON-строки в объект)
+
+Прежде чем делать десериализацию внимательно посмотрим на полученный JSON-файл. 
+
+Конструкция `"date":"\/Date(1609448400000+0300)\/"` специфична для C#, в стандарте JSON такого нет и в обычных API дата передается строкой.
+
+И так как нам предстоит общаться как раз с разными АПИ написанными отнюдь не на C#, то рассмотрим как десериализовать дату из строки.
+
+Десериализовать будем такой файл, в нём используется  наиболее распространенный формат даты:
+
+```json
+[
+    {"age":25,"name":"Иванов","date":"2021-03-21"},
+    {"age":35,"name":"Петров","date":"2021-03-22"}
+]
+```
+
+C# не поддерживает перегрузку свойств, поэтому мы не можем определить одноименные свойства с разными типами.
+
+Решение есть в атрибутах контракта и в том, что сериализатор "видит" и приватные свойства класса:
+
+* `[IgnoreDataMember]` - этот атрибут скрывает отмеченное свойство от сериализатора
+* `[DataMember(Name = "альтернативное название")]` - можно задать альтернативное имя для сериализуемого свойства.
+
+Перепишем класс Person с учетом этих атрибутов:
+
+```cs
+[DataContract]
+internal class Person
+{
+    // создаем приватную переменную для хранения даты
+    private DateTime privateDate;
+
+    [DataMember]
+    public string name { get; set; }
+
+    [DataMember]
+    public string age { get; set; }
+
+    // создаем ПРИВАТНОЕ СТРОКОВОЕ свойство и с помощью атрибутов меняем ему имя для сериализатора
+    [DataMember(Name = "date")]
+    private string StringDate {
+        get {
+            return privateDate.ToString("yyyy-MM-dd");
+        }
+        set {
+            // 2021-03-21
+            // 0123456789
+            privateDate = new DateTime(
+                Convert.ToInt32(value.Substring(0, 4)),
+                Convert.ToInt32(value.Substring(5, 2)),
+                Convert.ToInt32(value.Substring(8, 2))
+            );
+        } 
+    }
+
+    // публичное свойство "дата" скрываем от сериализатора
+    [IgnoreDataMember]
+    public DateTime date { 
+        get { return privateDate; }
+        set { privateDate = value;  } 
+    }
+}
+```
+
+Таким образом при десериализации при задании свойства "дата" будет вызван сеттер свойства *StringDate*. А при использовании объекта **Person** в коде его публичное свойство **date**.
+
+Десериализация делается методом *ReadObject*, который на входе принимает поток с JSON-строкой.
+
+```cs
+var Serializer = new DataContractJsonSerializer(typeof(Person[]));
+using(var sr = new StreamReader("test.json"))
+{
+    var PersonList = (Person[])Serializer.ReadObject(sr.BaseStream);
+    ...
+```
+
+## Вариант попроще
+
+Можно использовать не только "голый" **.NET Framework**, но и библиотеки из других компонентов **Visual Studio**.
+
+В пространстве имён **System.Web.Script.Serialization** есть класс **JavaScriptSerializer**, который выглядит попроще чем классическая реализация:
+
+>В пакет разработки C# не входит библиотека **System.Web.Extensions** (в которой и находится **System.Web.Script.Serialization**). Нужно в "Обозревателе решений" добавить в "Ссылки" библиотеку *Сборки -> Платформа -> System.Web.Extensions*
+
+```cs
+// целевые классы нам по прежнему нужны, но уже без всяких аннотаций
+internal class MaterialTC
+{
+    public string Title { get; set; }
+    public int Count { get; set; }
+}
+
+internal class Notice
+{
+    public Material[] data;
+}
+
+internal class Answer
+{
+    public Notice notice;
+}
+
+
+// в месте, где нам нужно распарсить JSON создаем сериализатор и разбираем строку
+var serializer = new JavaScriptSerializer();
+var answer = serializer.Deserialize<Answer>("тут ваша JSON-строка");
+
+// и ВСЁ
+```
+
+---
+
+## Задание на дом:
+
+Реализовать примеры из лекции. Привести текст примера и текст результата.
+
+XML делать не надо, только CSV и JSON. Но для JSON реализовать и сереализацию и десереализацию.
+
+(Тут примеры сериализации и десереализации)[https://www.newtonsoft.com/json/help/html/SerializeObject.htm]
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Работа с файловой системой и файлами.](./t5_files.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [Регулярные выражения](./t5_regex.md)

+ 1393 - 0
articles/t5_files.md

@@ -0,0 +1,1393 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Многопоточность. Потоки, асинхронные вычисления](./t5_thread_async.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [Типы файлов: CSV, XML, JSON.](./t5_file_types.md)
+
+# Работа с потоками (stream) и файловой системой
+
+Большинство задач в программировании так или иначе связаны с работой с файлами и каталогами. Нам может потребоваться прочитать текст из файла или наоборот произвести запись, удалить файл или целый каталог, не говоря уже о более комплексных задачах, как например, создание текстового редактора и других подобных задачах.
+
+Фреймворк .NET предоставляет большие возможности по управлению и манипуляции файлами и каталогами, которые по большей части сосредоточены в пространстве имен `System.IO`. Классы, расположенные в этом пространстве имен (такие как **Stream**, **StreamWriter**, **FileStream** и др.), позволяют управлять файловым вводом-выводом.
+
+## Работа с дисками
+
+Работу с файловой системой начнем с самого верхнего уровня - дисков. Для представления диска в пространстве имен `System.IO` имеется класс **DriveInfo**.
+
+Этот класс имеет статический метод _GetDrives_, который возвращает имена всех логических дисков компьютера. Также он предоставляет ряд полезных свойств:
+
+* *AvailableFreeSpace*: указывает на объем доступного свободного места на диске в байтах
+* *DriveFormat*: получает имя файловой системы
+* *DriveType*: представляет тип диска
+* *IsReady*: готов ли диск (например, DVD-диск может быть не вставлен в дисковод)
+* *Name*: получает имя диска
+* *TotalFreeSpace*: получает общий объем свободного места на диске в байтах
+* *TotalSize*: общий размер диска в байтах
+* *VolumeLabel*: получает или устанавливает метку тома
+
+Получим имена и свойства всех дисков на компьютере:
+
+```cs
+using System;
+using System.IO;
+ 
+DriveInfo[] drives = DriveInfo.GetDrives();
+
+foreach (DriveInfo drive in drives)
+{
+    Console.WriteLine($"Название: {drive.Name}");
+    Console.WriteLine($"Тип: {drive.DriveType}");
+    if (drive.IsReady)
+    {
+        Console.WriteLine($"Объем диска: {drive.TotalSize}");
+        Console.WriteLine($"Свободное пространство: {drive.TotalFreeSpace}");
+        Console.WriteLine($"Метка: {drive.VolumeLabel}");
+    }
+    Console.WriteLine();
+}
+```
+
+>У меня на компьютере Linux, поэтому дисков оказалось очень много, под Windows скорее всего будет один
+
+```
+/home/kei/RiderProjects/OAP/stream/bin/Debug/net8.0/stream
+Название: /proc
+Тип: Ram
+Объем диска: 0
+Свободное пространство: 0
+Метка: /proc
+
+Название: /sys
+Тип: Ram
+Объем диска: 0
+Свободное пространство: 0
+Метка: /sys
+
+Название: /dev
+Тип: Ram
+Объем диска: 8236191744
+Свободное пространство: 8236191744
+Метка: /dev
+
+Название: /run
+Тип: Ram
+Объем диска: 8254484480
+Свободное пространство: 8252055552
+Метка: /run
+
+Название: /sys/firmware/efi/efivars
+Тип: Unknown
+Объем диска: 188328
+Свободное пространство: 107141
+Метка: /sys/firmware/efi/efivars
+
+Название: /dev/pts
+Тип: Ram
+Объем диска: 0
+Свободное пространство: 0
+Метка: /dev/pts
+
+Название: /
+Тип: Fixed
+Объем диска: 1006662447104
+Свободное пространство: 862912659456
+Метка: /
+
+Название: /sys/kernel/security
+Тип: Ram
+Объем диска: 0
+Свободное пространство: 0
+Метка: /sys/kernel/security
+
+Название: /dev/shm
+Тип: Ram
+Объем диска: 8254484480
+Свободное пространство: 8211234816
+Метка: /dev/shm
+
+Название: /sys/fs/cgroup
+Тип: Ram
+Объем диска: 0
+Свободное пространство: 0
+Метка: /sys/fs/cgroup
+
+Название: /sys/fs/pstore
+Тип: Fixed
+Объем диска: 0
+Свободное пространство: 0
+Метка: /sys/fs/pstore
+
+Название: /sys/fs/bpf
+Тип: Fixed
+Объем диска: 0
+Свободное пространство: 0
+Метка: /sys/fs/bpf
+
+Название: /proc/sys/fs/binfmt_misc
+Тип: Ram
+Объем диска: 0
+Свободное пространство: 0
+Метка: /proc/sys/fs/binfmt_misc
+
+Название: /dev/mqueue
+Тип: Ram
+Объем диска: 0
+Свободное пространство: 0
+Метка: /dev/mqueue
+
+Название: /dev/hugepages
+Тип: Ram
+Объем диска: 0
+Свободное пространство: 0
+Метка: /dev/hugepages
+
+Название: /sys/kernel/debug
+Тип: Ram
+Объем диска: 0
+Свободное пространство: 0
+Метка: /sys/kernel/debug
+
+Название: /sys/kernel/tracing
+Тип: Fixed
+Объем диска: 0
+Свободное пространство: 0
+Метка: /sys/kernel/tracing
+
+Название: /sys/fs/fuse/connections
+Тип: Ram
+Объем диска: 0
+Свободное пространство: 0
+Метка: /sys/fs/fuse/connections
+
+Название: /sys/kernel/config
+Тип: Ram
+Объем диска: 0
+Свободное пространство: 0
+Метка: /sys/kernel/config
+
+Название: /tmp
+Тип: Ram
+Объем диска: 8254484480
+Свободное пространство: 8238710784
+Метка: /tmp
+
+Название: /boot/efi
+Тип: Fixed
+Объем диска: 313942016
+Свободное пространство: 302096384
+Метка: /boot/efi
+
+Название: /var/lib/docker/overlay2/4a2802316a49f1a30573524d0059ce49f6cd6cfe40608b77efb6febce3b2d66d/merged
+Тип: Unknown
+
+Название: /var/lib/docker/overlay2/a91156ea1396c9882176076645afbc6b4c5ded5492d9226442a6c5b55d07fd67/merged
+Тип: Unknown
+
+Название: /var/lib/docker/overlay2/de7b5cae3507be9b60ff7a3ddb8375e6968f465e3d926eb1c56c0206c8e2e17a/merged
+Тип: Unknown
+
+Название: /var/lib/docker/overlay2/f6ab6ef9b79dabd97d12c898d4239d1fb173ebcd83a1abf5c22704e730390115/merged
+Тип: Unknown
+
+Название: /var/lib/docker/overlay2/cf4ba0731412147587fe830cbcc73005b3f660443a73da88977222e55c304e27/merged
+Тип: Unknown
+
+Название: /run/docker/netns/default
+Тип: Unknown
+
+Название: /run/docker/netns/fe8e9119c528
+Тип: Unknown
+
+Название: /run/user/1000
+Тип: Ram
+Объем диска: 1650896896
+Свободное пространство: 1650798592
+Метка: /run/user/1000
+
+Название: /run/user/1000/doc
+Тип: Unknown
+Unhandled exception. System.UnauthorizedAccessException: Access to the path is denied.
+ ---> System.IO.IOException: Operation not permitted
+   --- End of inner exception stack trace ---
+   at System.IO.DriveInfo.CheckStatfsResultAndThrowIfNecessary(Int32 result)
+   at System.IO.DriveInfo.get_TotalSize()
+   at Program.<Main>$(String[] args) in /home/kei/RiderProjects/OAP/stream/Program.cs:line 9
+
+Process finished with exit code 134.
+```
+
+## Работа с каталогами
+
+Для работы с каталогами в пространстве имен `System.IO` предназначены сразу два класса: **Directory** и **DirectoryInfo**.
+
+### Класс **Directory**
+
+Класс **Directory** предоставляет ряд статических методов для управления каталогами. Некоторые из этих методов:
+
+* *CreateDirectory(path)*: создает каталог по указанному пути path
+* *Delete(path)*: удаляет каталог по указанному пути path
+* *Exists(path)*: определяет, существует ли каталог по указанному пути path. Если существует, возвращается true, если не существует, то false
+* *GetDirectories(path)*: получает список каталогов в каталоге path
+* *GetFiles(path)*: получает список файлов в каталоге path
+* *Move(sourceDirName, destDirName)*: перемещает каталог
+* *GetParent(path)*: получение родительского каталога
+
+### Класс **DirectoryInfo**
+
+Данный класс предоставляет функциональность для создания, удаления, перемещения и других операций с каталогами. Во многом он похож на **Directory**. Некоторые из его свойств и методов:
+
+* *Create()*: создает каталог
+* *CreateSubdirectory(path)*: создает подкаталог по указанному пути path
+* *Delete()*: удаляет каталог
+* Свойство *Exists*: определяет, существует ли каталог
+* *GetDirectories()*: получает список каталогов
+* *GetFiles()*: получает список файлов
+* *MoveTo(destDirName)*: перемещает каталог
+* Свойство *Parent*: получение родительского каталога
+* Свойство *Root*: получение корневого каталога
+
+Посмотрим на примерах применение этих классов
+
+#### Получение списка файлов и подкаталогов
+
+```cs
+string dirName = "C:\\";
+ 
+if (Directory.Exists(dirName))
+{
+    Console.WriteLine("Подкаталоги:");
+    string[] dirs = Directory.GetDirectories(dirName);
+    foreach (string s in dirs)
+    {
+        Console.WriteLine(s);
+    }
+    Console.WriteLine();
+    Console.WriteLine("Файлы:");
+    string[] files = Directory.GetFiles(dirName);
+    foreach (string s in files)
+    {
+        Console.WriteLine(s);
+    }
+}
+```
+
+Обратите внимание на использование слешей в именах файлов. Либо мы используем двойной слеш: `C:\\`, либо одинарный, но тогда перед строкой ставим знак `@`: `@"C:\Program Files"`
+
+>Можно вместо обратных слешей использовать прямые. Windows нормально их воспринимает
+
+#### Создание каталога
+
+```cs
+string path = @"C:\SomeDir";
+string subpath = @"program\avalon";
+DirectoryInfo dirInfo = new DirectoryInfo(path);
+if (!dirInfo.Exists)
+{
+    dirInfo.Create();
+}
+dirInfo.CreateSubdirectory(subpath);
+```
+
+Вначале проверяем, а нет ли такой директории, так как если она существует, то ее создать будет нельзя, и приложение выбросит ошибку. В итоге у нас получится следующий путь: `C:\SomeDir\program\avalon`
+
+#### Получение информации о каталоге
+
+```cs
+string dirName = "C:\\Program Files";
+ 
+DirectoryInfo dirInfo = new DirectoryInfo(dirName);
+ 
+Console.WriteLine($"Название каталога: {dirInfo.Name}");
+Console.WriteLine($"Полное название каталога: {dirInfo.FullName}");
+Console.WriteLine($"Время создания каталога: {dirInfo.CreationTime}");
+Console.WriteLine($"Корневой каталог: {dirInfo.Root}");
+```
+
+#### Удаление каталога
+
+Если мы просто применим метод _Delete_ к непустой папке, в которой есть какие-нибудь файлы или подкаталоги, то приложение нам выбросит ошибку. Поэтому нам надо передать в метод _Delete_ дополнительный параметр булевого типа, который укажет, что папку надо удалять со всем содержимым:
+
+```cs
+string dirName = @"C:\SomeFolder";
+ 
+try
+{
+    DirectoryInfo dirInfo = new DirectoryInfo(dirName);
+    dirInfo.Delete(true);
+    Console.WriteLine("Каталог удален");
+}
+catch (Exception ex)
+{
+    Console.WriteLine(ex.Message);
+}
+```
+
+Или так:
+
+```cs
+string dirName = @"C:\SomeFolder";
+ 
+Directory.Delete(dirName, true);
+```
+
+#### Перемещение каталога
+
+```cs
+string oldPath = @"C:\SomeFolder";
+string newPath = @"C:\SomeDir";
+DirectoryInfo dirInfo = new DirectoryInfo(oldPath);
+if (dirInfo.Exists && Directory.Exists(newPath) == false)
+{
+    dirInfo.MoveTo(newPath);
+}
+```
+
+При перемещении надо учитывать, что новый каталог, в который мы хотим перемесить все содержимое старого каталога, не должен существовать.
+
+## Работа с файлами. Классы **File** и **FileInfo**
+
+Подобно паре **Directory**/**DirectoryInfo** для работы с файлами предназначена пара классов **File** и **FileInfo**. С их помощью мы можем создавать, удалять, перемещать файлы, получать их свойства и многое другое.
+
+Некоторые полезные методы и свойства класса **FileInfo**:
+
+* *CopyTo(path)*: копирует файл в новое место по указанному пути path
+* *Create()*: создает файл
+* *Delete()*: удаляет файл
+* *MoveTo(destFileName)*: перемещает файл в новое место
+* Свойство *Directory*: получает родительский каталог в виде объекта DirectoryInfo
+* Свойство *DirectoryName*: получает полный путь к родительскому каталогу
+* Свойство *Exists*: указывает, существует ли файл
+* Свойство *Length*: получает размер файла
+* Свойство *Extension*: получает расширение файла
+* Свойство *Name*: получает имя файла
+* Свойство *FullName*: получает полное имя файла
+
+Класс **File** реализует похожую функциональность с помощью статических методов:
+
+* *Copy()*: копирует файл в новое место
+* *Create()*: создает файл
+* *Delete()*: удаляет файл
+* *Move*: перемещает файл в новое место
+* *Exists(file)*: определяет, существует ли файл
+
+### Получение информации о файле
+
+```cs
+string path = @"C:\apache\hta.txt";
+FileInfo fileInf = new FileInfo(path);
+if (fileInf.Exists)
+{
+    Console.WriteLine("Имя файла: {0}", fileInf.Name);
+    Console.WriteLine("Время создания: {0}", fileInf.CreationTime);
+    Console.WriteLine("Размер: {0}", fileInf.Length);
+}
+```
+
+### Удаление файла
+
+```cs
+string path = @"C:\apache\hta.txt";
+FileInfo fileInf = new FileInfo(path);
+if (fileInf.Exists)
+{
+   fileInf.Delete();
+   // альтернатива с помощью класса File
+   // File.Delete(path);
+}
+```
+
+### Перемещение файла
+
+```cs
+string path = @"C:\apache\hta.txt";
+string newPath = @"C:\SomeDir\hta.txt";
+FileInfo fileInf = new FileInfo(path);
+if (fileInf.Exists)
+{
+   fileInf.MoveTo(newPath);       
+   // альтернатива с помощью класса File
+   // File.Move(path, newPath);
+}
+```
+
+### Копирование файла
+
+```cs
+string path = @"C:\apache\hta.txt";
+string newPath = @"C:\SomeDir\hta.txt";
+FileInfo fileInf = new FileInfo(path);
+if (fileInf.Exists)
+{
+   fileInf.CopyTo(newPath, true);      
+   // альтернатива с помощью класса File
+   // File.Copy(path, newPath, true);
+}
+```
+
+Метод *CopyTo* класса **FileInfo** принимает два параметра: путь, по которому файл будет копироваться, и булевое значение, которое указывает, надо ли при копировании перезаписывать файл (если **true**, как в случае выше, файл при копировании перезаписывается). Если же в качестве последнего параметра передать значение **false**, то если такой файл уже существует, приложение выдаст ошибку.
+
+Метод *Copy* класса **File** принимает три параметра: путь к исходному файлу, путь, по которому файл будет копироваться, и булевое значение, указывающее, будет ли файл перезаписываться.
+
+## **FileStream**. Чтение и запись файла
+
+Класс **FileStream** представляет возможности по считыванию из файла и записи в файл. Он позволяет работать как с текстовыми файлами, так и с бинарными.
+
+### Создание **FileStream**
+
+Для создания объекта **FileStream** можно использовать как конструкторы этого класса, так и статические методы класса **File**. Конструктор **FileStream** имеет множество перегруженных версий, из которых отмечу лишь одну, самую простую и используемую:
+
+```cs
+FileStream(string filename, FileMode mode)
+```
+
+Здесь в конструктор передается два параметра: путь к файлу и перечисление (**enum**) *FileMode*. Данное перечисление указывает на режим доступа к файлу и может принимать следующие значения:
+
+* *Append*: если файл существует, то текст добавляется в конец файл. Если файла нет, то он создается. Файл открывается только для записи.
+* *Create*: создается новый файл. Если такой файл уже существует, то он перезаписывается
+* *CreateNew*: создается новый файл. Если такой файл уже существует, то он приложение выбрасывает ошибку
+* *Open*: открывает файл. Если файл не существует, выбрасывается исключение
+* *OpenOrCreate*: если файл существует, он открывается, если нет - создается новый
+* *Truncate*: если файл существует, то он перезаписывается. Файл открывается только для записи.
+
+Другой способ создания объекта *FileStream* представляют статические методы класса *File*:
+
+```cs
+FileStream File.Open(string file, FileMode mode);
+FileStream File.OpenRead(string file);
+FileStream File.OpenWrite(string file);
+```
+
+Первый метод открывает файл с учетом объекта *FileMode* и возвращает файловой поток **FileStream**. У этого метода также есть несколько перегруженных версий. Второй метод открывает поток для чтения, а третий открывает поток для записи.
+
+### Свойства и методы **FileStream**
+
+Рассмотрим наиболее важные его свойства и методы класса **FileStream**:
+
+* Свойство *Length*: возвращает длину потока в байтах
+* Свойство *Position*: возвращает текущую позицию в потоке
+* *void CopyTo(Stream destination)*: копирует данные из текущего потока в поток destination
+* *Task CopyToAsync(Stream destination)*: асинхронная версия метода CopyToAsync
+* *int Read(byte[] array, int offset, int count)*: считывает данные из файла в массив байтов и возвращает количество успешно считанных байтов. Принимает три параметра:
+
+    * *array* - массив байтов, куда будут помещены считываемые из файла данные
+
+    * *offset* представляет смещение в байтах в массиве array, в который считанные байты будут помещены
+
+    * *count* - максимальное число байтов, предназначенных для чтения. Если в файле находится меньшее количество байтов, то все они будут считаны.
+
+* `Task<int> ReadAsync(byte[] array, int offset, int count)`: асинхронная версия метода Read
+
+* `long Seek(long offset, SeekOrigin origin)`: устанавливает позицию в потоке со смещением на количество байт, указанных в параметре *offset*.
+
+* `void Write(byte[] array, int offset, int count)`: записывает в файл данные из массива байтов. Принимает три параметра:
+
+    * *array* - массив байтов, откуда данные будут записываться в файл
+
+    * *offset* - смещение в байтах в массиве array, откуда начинается запись байтов в поток
+
+    * *count* - максимальное число байтов, предназначенных для записи
+
+* `ValueTask WriteAsync(byte[] array, int offset, int count)`: асинхронная версия метода Write
+
+### Чтение и запись файлов
+
+**FileStream** представляет доступ к файлам на уровне байтов, поэтому, например, если вам надо считать или записать одну или несколько строк в текстовый файл, то массив байтов надо преобразовать в строки, используя специальные методы. Поэтому для работы с текстовыми файлами применяются другие классы.
+
+В то же время при работе с различными бинарными файлами, имеющими определенную структуру, **FileStream** может быть очень даже полезен для извлечения определенных порций информации и ее обработки.
+
+Посмотрим на примере считывания-записи в текстовый файл:
+
+```cs
+using System;
+using System.IO;
+ 
+namespace HelloApp
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            // создаем каталог для файла
+            string path = @"C:\SomeDir2";
+            DirectoryInfo dirInfo = new DirectoryInfo(path);
+            if (!dirInfo.Exists)
+            {
+                dirInfo.Create();
+            }
+            Console.WriteLine("Введите строку для записи в файл:");
+            string text = Console.ReadLine();
+ 
+            // запись в файл
+            using (FileStream fstream = new FileStream($"{path}\note.txt", FileMode.OpenOrCreate))
+            {
+                // преобразуем строку в байты
+                byte[] array = System.Text.Encoding.Default.GetBytes(text);
+                // запись массива байтов в файл
+                fstream.Write(array, 0, array.Length);
+                Console.WriteLine("Текст записан в файл");
+            }
+ 
+            // чтение из файла
+            using (FileStream fstream = File.OpenRead($"{path}\note.txt"))
+            {
+                // преобразуем строку в байты
+                byte[] array = new byte[fstream.Length];
+                // считываем данные
+                fstream.Read(array, 0, array.Length);
+                // декодируем байты в строку
+                string textFromFile = System.Text.Encoding.Default.GetString(array);
+                Console.WriteLine($"Текст из файла: {textFromFile}");
+            }
+ 
+            Console.ReadLine();
+        }
+    }
+}
+``` 
+
+Разберем этот пример. Вначале создается папка для файла. Кроме того, на уровне операционной системы могут быть установлены ограничения на запись в определенных каталогах, и при попытке создания и записи файла в подобных каталогах мы получим ошибку.
+
+И при чтении, и при записи используется оператор **using**. Не надо путать данный оператор с директивой **using**, которая подключает пространства имен в начале файла кода. Оператор **using** позволяет создавать объект в блоке кода, по завершению которого вызывается метод *Dispose* у этого объекта, и, таким образом, объект уничтожается. В данном случае в качестве такого объекта служит переменная *fstream*.
+
+И при записи, и при чтении применяется объект кодировки `Encoding.Default` из пространства имен `System.Text`. В данном случае мы используем два его метода: *GetBytes* для получения массива байтов из строки и *GetString* для получения строки из массива байтов.
+
+В итоге введенная нами строка записывается в файл `note.txt`. По сути это бинарный файл (не текстовый), хотя если мы в него запишем только строку, то сможем посмотреть в удобочитаемом виде этот файл, открыв его в текстовом редакторе. Однако если мы в него запишем случайные байты, например:
+
+```cs
+fstream.WriteByte(13);
+fstream.WriteByte(103);
+```
+
+То у нас могут возникнуть проблемы с его пониманием. Поэтому для работы непосредственно с текстовыми файлами предназначены отдельные классы - *StreamReader* и *StreamWriter*.
+
+В реальных приложениях рекомендуется использовать асинхронные версии методов **FileStream**, поскольку операции с файлами могут занимать продолжительное время и являются узким местом в работе программы. Например, изменим выше приведенную программу, применив асинхронные методы:
+
+```cs
+using System;
+using System.IO;
+using System.Threading.Tasks;
+ 
+namespace HelloApp
+{
+    class Program
+    {
+        static async Task Main(string[] args)
+        {
+            // создаем каталог для файла
+            string path = @"C:\SomeDir3";
+            DirectoryInfo dirInfo = new DirectoryInfo(path);
+            if (!dirInfo.Exists)
+            {
+                dirInfo.Create();
+            }
+            Console.WriteLine("Введите строку для записи в файл:");
+            string text = Console.ReadLine();
+ 
+            // запись в файл
+            using (FileStream fstream = new FileStream($"{path}\note.txt", FileMode.OpenOrCreate))
+            {
+                byte[] array = System.Text.Encoding.Default.GetBytes(text);
+                // асинхронная запись массива байтов в файл
+                await fstream.WriteAsync(array, 0, array.Length);
+                Console.WriteLine("Текст записан в файл");
+            }
+ 
+            // чтение из файла
+            using (FileStream fstream = File.OpenRead($"{path}\note.txt"))
+            {
+                byte[] array = new byte[fstream.Length];
+                // асинхронное чтение файла
+                await fstream.ReadAsync(array, 0, array.Length);
+ 
+                string textFromFile = System.Text.Encoding.Default.GetString(array);
+                Console.WriteLine($"Текст из файла: {textFromFile}");
+            }
+ 
+            Console.ReadLine();
+        }
+    }
+}
+```
+
+### Произвольный доступ к файлам
+
+Нередко бинарные файлы представляют определенную структуру. И, зная эту структуру, мы можем взять из файла нужную порцию информации или наоброт записать в определенном месте файла определенный набор байтов. Например, в wav-файлах непосредственно звуковые данные начинаются с 44 байта, а до 44 байта идут различные метаданные - количество каналов аудио, частота дискретизации и т.д.
+
+С помощью метода *Seek* мы можем управлять положением курсора потока, начиная с которого производится считывание или запись в файл. Этот метод принимает два параметра: *offset* (смещение) и позиция в файле. Позиция в файле описывается тремя значениями:
+
+* `SeekOrigin.Begin`: начало файла
+* `SeekOrigin.End`: конец файла
+* `SeekOrigin.Current`: текущая позиция в файле
+
+Курсор потока, с которого начинается чтение или запись, смещается вперед на значение *offset* относительно позиции, указанной в качестве второго параметра. Смещение может быть отрицательным, тогда курсор сдвигается назад, если положительное - то вперед.
+
+Рассмотрим на примере:
+
+```cs
+using System.IO;
+using System.Text;
+ 
+class Program
+{
+    static void Main(string[] args)
+    {
+        string text = "hello world";
+             
+        // запись в файл
+        using (FileStream fstream = new FileStream(@"D:\note.dat", FileMode.OpenOrCreate))
+        {
+            // преобразуем строку в байты
+            byte[] input = Encoding.Default.GetBytes(text);
+            // запись массива байтов в файл
+            fstream.Write(input, 0, input.Length);
+            Console.WriteLine("Текст записан в файл");
+ 
+            // перемещаем указатель в конец файла, до конца файла- пять байт
+            fstream.Seek(-5, SeekOrigin.End); // минус 5 символов с конца потока
+ 
+            // считываем четыре символов с текущей позиции
+            byte[] output = new byte[4];
+            fstream.Read(output, 0, output.Length);
+            // декодируем байты в строку
+            string textFromFile = Encoding.Default.GetString(output);
+            Console.WriteLine($"Текст из файла: {textFromFile}"); // worl
+ 
+            // заменим в файле слово world на слово house
+            string replaceText = "house";
+            fstream.Seek(-5, SeekOrigin.End); // минус 5 символов с конца потока
+            input = Encoding.Default.GetBytes(replaceText);
+            fstream.Write(input, 0, input.Length);
+ 
+            // считываем весь файл
+            // возвращаем указатель в начало файла
+            fstream.Seek(0, SeekOrigin.Begin);
+            output = new byte[fstream.Length];
+            fstream.Read(output, 0, output.Length);
+            // декодируем байты в строку
+            textFromFile = Encoding.Default.GetString(output);
+            Console.WriteLine($"Текст из файла: {textFromFile}"); // hello house
+        }
+        Console.Read();
+    }
+}
+```
+
+Консольный вывод:
+
+```
+Текст записан в файл
+Текст из файла: worl
+Текст из файла: hello house
+```
+
+Вызов `fstream.Seek(-5, SeekOrigin.End)` перемещает курсор потока в конец файлов назад на пять символов:
+
+![](../img/05002.png)
+
+То есть после записи в новый файл строки "hello world" курсор будет стоять на позиции символа "w".
+
+После этого считываем четыре байта начиная с символа "w". В данной кодировке один символ будет представлять один байт. Поэтому чтение 4-х байтов будет эквивалентно чтению четырех сиволов: "worl".
+
+Затем опять же перемещаемся в конец файла, не доходя до конца пять символов (то есть опять же с позиции символа "w"), и осуществляем запись строки "house". Таким образом, строка "house" заменяет строку "world".
+
+### Закрытие потока
+
+В примерах выше для закрытия потока применяется конструкция **using**. После того как все операторы и выражения в блоке **using** отработают, объект **FileStream** уничтожается. Однако мы можем выбрать и другой способ:
+
+```cs
+FileStream fstream = null;
+try
+{
+    fstream = new FileStream(@"D:\note3.dat", FileMode.OpenOrCreate);
+    // операции с потоком
+}
+catch(Exception ex)
+{
+ 
+}
+finally
+{
+    if (fstream != null)
+        fstream.Close();
+}
+```
+
+Если мы не используем конструкцию **using**, то нам надо явным образом вызвать метод *Close*: `fstream.Close()`
+
+### Чтение и запись текстовых файлов. StreamReader и StreamWriter
+
+Класс **FileStream** не очень удобно применять для работы с текстовыми файлами. Для этого в пространстве `System.IO` определены специальные классы: **StreamReader** и **StreamWriter**.
+
+#### Запись в файл и **StreamWriter**
+
+Для записи в текстовый файл используется класс **StreamWriter**. Некоторые из его конструкторов, которые могут применяться для создания объекта **StreamWriter**:
+
+* `StreamWriter(string path)`: через параметр _path_ передается путь к файлу, который будет связан с потоком
+
+* `StreamWriter(string path, bool append)`: параметр _append_ указывает, надо ли добавлять в конец файла данные или же перезаписывать файл. Если равно **true**, то новые данные добавляются в конец файла. Если равно **false**, то файл перезаписываетсяя заново
+
+* `StreamWriter(string path, bool append, System.Text.Encoding encoding)`: параметр *encoding* указывает на кодировку, которая будет применяться при записи
+
+Свою функциональность **StreamWriter** реализует через следующие методы:
+
+* `int Close()`: закрывает записываемый файл и освобождает все ресурсы
+
+* `void Flush()`: записывает в файл оставшиеся в буфере данные и очищает буфер.
+
+* `Task FlushAsync()`: асинхронная версия метода _Flush_
+
+* `void Write(string value)`: записывает в файл данные простейших типов, как **int**, **double**, **char**, **string** и т.д. Соответственно имеет ряд перегруженных версий для записи данных элементарных типов, например, `Write(char value)`, `Write(int value)`, `Write(double value)` и т.д.
+
+* `Task WriteAsync(string value)`: асинхронная версия метода _Write_
+
+* `void WriteLine(string value)`: также записывает данные, только после записи добавляет в файл символ окончания строки
+
+* `Task WriteLineAsync(string value)`: асинхронная версия метода *WriteLine*
+
+Рассмотрим запись в файл на примере:
+
+```cs
+using System;
+using System.IO;
+ 
+namespace HelloApp
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            string writePath = @"C:\SomeDir\hta.txt";
+ 
+            string text = "Привет мир!\nПока мир...";
+            try
+            {
+                using (StreamWriter sw = new StreamWriter(
+                    writePath, false, System.Text.Encoding.Default))
+                {
+                    sw.WriteLine(text);
+                }
+ 
+                using (StreamWriter sw = new StreamWriter(
+                    writePath, true, System.Text.Encoding.Default))
+                {
+                    sw.WriteLine("Дозапись");
+                    sw.Write(4.5);
+                }
+                Console.WriteLine("Запись выполнена");
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine(e.Message);
+            }
+        }
+    }
+}
+```
+
+В данном случае два раза создаем объект **StreamWriter**. В первом случае если файл существует, то он будет перезаписан. Если не существует, он будет создан. И в нее будет записан текст из переменной *text*. Во втором случае файл открывается для дозаписи, и будут записаны атомарные данные - строка и число. В обоих случаях будет использоваться кодировка по умолчанию.
+
+По завершении программы в папке `C:/SomeDir` мы сможем найти файл `hta.txt`, который будет иметь следующие строки:
+
+```
+Привет мир!
+Пока мир...
+Дозапись
+4,5
+```
+
+Поскольку операции с файлами могут занимать продолжительное время, то в общем случае рекомендуется использовать асинхронную запись. Используем асинхронные версии методов:
+
+```cs
+using System;
+using System.IO;
+using System.Threading.Tasks;
+ 
+namespace HelloApp
+{
+    class Program
+    {
+        static async Task Main(string[] args)
+        {
+            string writePath = @"C:\SomeDir\hta2.txt";
+ 
+            string text = "Привет мир!\nПока мир...";
+            try
+            {
+                using (StreamWriter sw = new StreamWriter(
+                    writePath, false, System.Text.Encoding.Default))
+                {
+                    await sw.WriteLineAsync(text);
+                }
+ 
+                using (StreamWriter sw = new StreamWriter(
+                    writePath, true, System.Text.Encoding.Default))
+                {
+                    await sw.WriteLineAsync("Дозапись");
+                    await sw.WriteAsync("4,5");
+                }
+                Console.WriteLine("Запись выполнена");
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine(e.Message);
+            }
+        }
+    }
+}
+```
+
+Обратите внимание, что асинхронные версии есть не для всех перегрузок метода *Write*.
+
+#### Чтение из файла и **StreamReader**
+
+Класс **StreamReader** позволяет нам легко считывать весь текст или отдельные строки из текстового файла.
+
+Некоторые из конструкторов класса **StreamReader**:
+
+* `StreamReader(string path)`: через параметр _path_ передается путь к считываемому файлу
+
+* `StreamReader(string path, System.Text.Encoding encoding)`: параметр _encoding_ задает кодировку для чтения файла
+
+Среди методов **StreamReader** можно выделить следующие:
+
+* `void Close()`: закрывает считываемый файл и освобождает все ресурсы
+
+* `int Peek()`: возвращает следующий доступный символ, если символов больше нет, то возвращает `-1`
+
+* `int Read()`: считывает и возвращает следующий символ в численном представлении. Имеет перегруженную версию: `Read(char[] array, int index, int count)`, где *array* - массив, куда считываются символы, *index* - индекс в массиве *array*, начиная с которого записываются считываемые символы, и *count* - максимальное количество считываемых символов
+
+* `Task<int> ReadAsync()`: асинхронная версия метода *Read*
+
+* `string ReadLine()`: считывает одну строку в файле
+
+* `string ReadLineAsync()`: асинхронная версия метода _ReadLine_
+
+* `string ReadToEnd()`: считывает весь текст из файла
+
+* `string ReadToEndAsync()`: асинхронная версия метода _ReadToEnd_
+
+Сначала считаем текст полностью из ранее записанного файла:
+
+```cs
+using System;
+using System.IO;
+using System.Threading.Tasks;
+ 
+string path = @"C:\SomeDir\hta.txt";
+
+try
+{
+    using (StreamReader sr = new StreamReader(path))
+    {
+        Console.WriteLine(sr.ReadToEnd());
+    }
+    // асинхронное чтение
+    using (StreamReader sr = new StreamReader(path))
+    {
+        Console.WriteLine(await sr.ReadToEndAsync());
+    }
+}
+catch (Exception e)
+{
+    Console.WriteLine(e.Message);
+}
+```
+
+Считаем текст из файла построчно:
+
+```cs
+string path= @"C:\SomeDir\hta.txt";
+   
+using (StreamReader sr = new StreamReader(path, System.Text.Encoding.Default))
+{
+    string line;
+    while ((line = sr.ReadLine()) != null)
+    {
+        Console.WriteLine(line);
+    }
+}
+// асинхронное чтение
+using (StreamReader sr = new StreamReader(path, System.Text.Encoding.Default))
+{
+    string line;
+    while ((line = await sr.ReadLineAsync()) != null)
+    {
+        Console.WriteLine(line);
+    }
+}
+```
+
+В данном случае считываем построчно через цикл **while**: `while ((line = sr.ReadLine()) != null)` - сначала присваиваем переменной *line* результат функции `sr.ReadLine()`, а затем проверяем, не равна ли она **null**. Когда объект `sr` дойдет до конца файла и больше строк не останется, то метод `sr.ReadLine()` будет возвращать null.
+
+## Бинарные файлы. **BinaryWriter** и **BinaryReader**
+
+Для работы с бинарными файлами предназначена пара классов **BinaryWriter** и **BinaryReader**. Эти классы позволяют читать и записывать данные в двоичном формате.
+
+Основные метода класса **BinaryWriter**
+
+* `Close()`: закрывает поток и освобождает ресурсы
+
+* `Flush()`: очищает буфер, дописывая из него оставшиеся данные в файл
+
+* `Seek()`: устанавливает позицию в потоке
+
+* `Write()`: записывает данные в поток
+
+Основные метода класса **BinaryReader**
+
+* `Close()`: закрывает поток и освобождает ресурсы
+
+* `ReadBoolean()`: считывает значение **bool** и перемещает указатель на один байт
+
+* *ReadByte()*: считывает один байт и перемещает указатель на один байт
+
+* `ReadChar()`: считывает значение **char**, то есть один символ, и перемещает указатель на столько байтов, сколько занимает символ в текущей кодировке
+
+* `ReadDecimal()`: считывает значение **decimal** и перемещает указатель на 16 байт
+
+* `ReadDouble()`: считывает значение **double** и перемещает указатель на 8 байт
+
+* `ReadInt16()`: считывает значение **short** и перемещает указатель на 2 байта
+
+* `ReadInt32()`: считывает значение **int** и перемещает указатель на 4 байта
+
+* `ReadInt64()`: считывает значение **long** и перемещает указатель на 8 байт
+
+* `ReadSingle()`: считывает значение *float* и перемещает указатель на 4 байта
+
+* `ReadString()`: считывает значение **string**. Каждая строка предваряется значением длины строки, которое представляет 7-битное целое число
+
+С чтением бинарных данных все просто: соответствующий метод считывает данные определенного типа и перемещает указатель на размер этого типа в байтах, например, значение типа **int** занимает 4 байта, поэтому **BinaryReader** считает 4 байта и переместит указать на эти 4 байта.
+
+Посмотрим на реальной задаче применение этих классов. Попробуем с их помощью записывать и считывать из файла массив структур:
+
+```cs
+struct State
+{
+    public string name;
+    public string capital;
+    public int area;
+    public double people;
+ 
+    public State(string n, string c, int a, double p)
+    {
+        name = n;
+        capital = c;
+        people = p;
+        area = a;
+    }
+}
+class Program
+{
+    static void Main(string[] args)
+    {
+        State[] states = new State[2];
+        states[0] = new State("Германия", "Берлин",  357168,  80.8);
+        states[1] = new State("Франция", "Париж", 640679, 64.7);
+ 
+        string path= @"C:\SomeDir\states.dat";
+ 
+        try
+        {
+            // создаем объект BinaryWriter
+            using (BinaryWriter writer = new BinaryWriter(File.Open(path, FileMode.OpenOrCreate)))
+            {
+                // записываем в файл значение каждого поля структуры
+                foreach (State s in states)
+                {
+                    writer.Write(s.name);
+                    writer.Write(s.capital);
+                    writer.Write(s.area);
+                    writer.Write(s.people);
+                }
+            }
+            // создаем объект BinaryReader
+            using (BinaryReader reader = new BinaryReader(File.Open(path, FileMode.Open)))
+            {
+                // пока не достигнут конец файла
+                // считываем каждое значение из файла
+                while (reader.PeekChar() > -1)
+                {
+                    string name = reader.ReadString();
+                    string capital = reader.ReadString();
+                    int area = reader.ReadInt32();
+                    double population = reader.ReadDouble();
+ 
+                    Console.WriteLine("Страна: {0}  столица: {1}  площадь {2} кв. км   численность населения: {3} млн. чел.", 
+                        name, capital, area, population);
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            Console.WriteLine(e.Message);
+        }
+        Console.ReadLine();
+    }
+}
+```
+
+Итак, у нас есть структура **State** с некоторым набором полей. В основной программе создаем массив структур и записываем с помощью **BinaryWriter**. Этот класс в качестве параметра в конструкторе принимает объект **Stream**, который создается вызовом `File.Open(path, FileMode.OpenOrCreate)`.
+
+Затем в цикле пробегаемся по массиву структур и записываем каждое поле структуры в поток. В том порядке, в каком эти значения полей записываются, в том порядке они и будут размещаться в файле.
+
+Затем считываем из записанного файла. Конструктор класса **BinaryReader** также в качестве параметра принимает объект потока, только в данном случае устанавливаем в качестве режима _FileMode.Open_: `new BinaryReader(File.Open(path, FileMode.Open))`
+
+В цикле **while** считываем данные. Чтобы узнать окончание потока, вызываем метод *PeekChar*. Этот метод считывает следующий символ и возвращает его числовое представление. Если символ отсутствует, то метод возвращает `-1`, что будет означать, что мы достигли конца файла.
+
+В цикле последовательно считываем значения поле структур в том же порядке, в каком они записывались.
+
+Таким образом, классы **BinaryWriter** и **BinaryReader** очень удобны для работы с бинарными файлами, особенно когда нам известна структура этих файлов. В то же время для хранения и считывания более комплексных объектов, например, объектов классов, лучше подходит другое решение - сериализация.
+
+## Бинарная сериализация. **BinaryFormatter**
+
+В прошлых темах было рассмотрено как сохранять и считывать информацию с текстовых и бинарных файлов с помощью классов из пространства `System.IO`. Но .NET также предоставляет еще один механизм для удобной работы с бинарными файлами и их данными - бинарную **сериализацию**. **Сериализация** представляет процесс преобразования какого-либо объекта в поток байтов. После преобразования мы можем этот поток байтов или записать на диск или сохранить его временно в памяти. А при необходимости можно выполнить обратный процесс - **десериализацию**, то есть получить из потока байтов ранее сохраненный объект.
+
+### Атрибут **Serializable**
+
+Чтобы объект определенного класса можно было сериализовать, надо этот класс пометить атрибутом **Serializable**:
+
+```cs
+[Serializable]
+class Person
+{
+    public string Name { get; set; }
+    public int Year { get; set; }
+ 
+    public Person(string name, int year)
+    {
+        Name = name;
+        Year = year;
+    }
+}
+```
+
+При отстутствии данного атрибута объект **Person** не сможет быть сериализован, и при попытке сериализации будет выброшено исключение **SerializationException**.
+
+Сериализация применяется к свойствам и полям класса. Если мы не хотим, чтобы какое-то поле класса сериализовалось, то мы его помечаем атрибутом **NonSerialized**:
+
+```cs
+[Serializable]
+class Person
+{
+    public string Name { get; set; }
+    public int Year { get; set; }
+     
+    [NonSerialized]
+    public string accNumber;
+     
+    public Person(string name, int year, string acc)
+    {
+        Name = name;
+        Year = year;
+        accNumber = acc;
+    }
+}
+```
+
+При наследовании подобного класса, следует учитывать, что атрибут **Serializable** автоматически не наследуется. И если мы хотим, чтобы производный класс также мог бы быть сериализован, то опять же мы применяем к нему атрибут:
+
+```cs
+[Serializable]
+class Worker : Person
+```
+
+Для бинарной сериализации применяется класс **BinaryFormatter**:
+
+```cs
+using System;
+using System.IO;
+using System.Runtime.Serialization.Formatters.Binary;
+ 
+namespace Serialization
+{
+    [Serializable]
+    class Person
+    {
+        public string Name { get; set; }
+        public int Age { get; set; }
+ 
+        public Person(string name, int age)
+        {
+            Name = name;
+            Age = age;
+        }
+    }
+     
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            // объект для сериализации
+            Person person = new Person("Tom", 29);
+            Console.WriteLine("Объект создан");
+ 
+            // создаем объект BinaryFormatter
+            BinaryFormatter formatter = new BinaryFormatter();
+            // получаем поток, куда будем записывать сериализованный объект
+            using (FileStream fs = new FileStream("people.dat", FileMode.OpenOrCreate))
+            {
+                formatter.Serialize(fs, person);
+ 
+                Console.WriteLine("Объект сериализован");
+            }
+ 
+            // десериализация из файла people.dat
+            using (FileStream fs = new FileStream("people.dat", FileMode.OpenOrCreate))
+            {
+                Person newPerson = (Person)formatter.Deserialize(fs);
+ 
+                Console.WriteLine("Объект десериализован");
+                Console.WriteLine($"Имя: {newPerson.Name} --- Возраст: {newPerson.Age}");
+            }
+ 
+            Console.ReadLine();
+        }
+    }
+}
+```
+
+Так как класс **BinaryFormatter** определен в пространстве имен `System.Runtime.Serialization.Formatters.Binary`, то в самом начале подключаем его.
+
+У нас есть простенький класс **Person**, который объявлен с атрибутом **Serializable**. Благодаря этому его объекты будут доступны для сериализации.
+
+Далее создаем объект *BinaryFormatter*: `BinaryFormatter formatter = new BinaryFormatter();`
+
+Затем последовательно выполняем сериализацию и десериализацию. Для обоих операций нам нужен поток, в который либо сохранять, либо из которого считывать данные. Данный поток представляет объект **FileStream**, который записывает нужный нам объект **Person** в файл `people.dat`.
+
+Сериализация одним методом `formatter.Serialize(fs, person)` добавляет все данные об объекте **Person** в файл `people.dat`.
+
+При десериализации нам нужно еще преобразовать объект, возвращаемый функцией *Deserialize*, к типу **Person**: `(Person)formatter.Deserialize(fs)`.
+
+Как вы видите, **сериализация** значительно упрощает процесс сохранения объектов в бинарную форму по сравнению, например, с использованием связки классов **BinaryWriter**/**BinaryReader**.
+
+Хотя мы взяли лишь один объект **Person**, но равным образом мы можем использовать и массив подобных объектов, список или иную коллекцию, к которой применяется атрибут **Serializable**. Посмотрим на примере массива:
+
+```cs
+Person person1 = new Person("Tom", 29);
+Person person2 = new Person("Bill", 25);
+// массив для сериализации
+Person[] people = new Person[] { person1, person2 };
+ 
+BinaryFormatter formatter = new BinaryFormatter();
+ 
+using (FileStream fs = new FileStream("people.dat", FileMode.OpenOrCreate))
+{
+    // сериализуем весь массив people
+    formatter.Serialize(fs, people);
+ 
+    Console.WriteLine("Объект сериализован");
+}
+ 
+// десериализация
+using (FileStream fs = new FileStream("people.dat", FileMode.OpenOrCreate))
+{
+    Person[] deserilizePeople = (Person[])formatter.Deserialize(fs);
+ 
+    foreach (Person p in deserilizePeople)
+    {
+        Console.WriteLine($"Имя: {p.Name} --- Возраст: {p.Age}");
+    }
+}
+```
+
+---
+
+## Задание на дом:
+
+Реализовать примеры из лекции. Привести текст примера и текст результата, например:
+
+># Конспект лекции "Работа с каталогами и файлами"
+>
+>## Список дисков
+>
+>```cs
+>using System;
+>using System.IO;
+> 
+>DriveInfo[] drives = DriveInfo.GetDrives();
+>
+>foreach (DriveInfo drive in drives)
+>{
+>    Console.WriteLine($"Название: {drive.Name}");
+>    Console.WriteLine($"Тип: {drive.DriveType}");
+>    if (drive.IsReady)
+>    {
+>        Console.WriteLine($"Объем диска: {drive.TotalSize}");
+>        Console.WriteLine($"Свободное пространство: {drive.TotalFreeSpace}");
+>        Console.WriteLine($"Метка: {drive.VolumeLabel}");
+>    }
+>    Console.WriteLine();
+>}
+>```
+>
+>Результат работы:
+>
+>```
+>/home/kei/RiderProjects/OAP/stream/bin/Debug/net8.0/stream
+>Название: /proc
+>Тип: Ram
+>Объем диска: 0
+>Свободное пространство: 0
+>Метка: /proc
+>
+>Название: /sys
+>Тип: Ram
+>Объем диска: 0
+>Свободное пространство: 0
+>Метка: /sys
+>
+>Название: /dev
+>Тип: Ram
+>Объем диска: 8236191744
+>Свободное пространство: 8236191744
+>Метка: /dev
+>
+>Название: /run
+>Тип: Ram
+>Объем диска: 8254484480
+>Свободное пространство: 8252055552
+>Метка: /run
+>
+>Название: /sys/firmware/efi/efivars
+>Тип: Unknown
+>Объем диска: 188328
+>Свободное пространство: 107141
+>Метка: /sys/firmware/efi/efivars
+>
+>Название: /dev/pts
+>Тип: Ram
+>Объем диска: 0
+>Свободное пространство: 0
+>Метка: /dev/pts
+>
+>Название: /
+>Тип: Fixed
+>Объем диска: 1006662447104
+>Свободное пространство: 862912659456
+>Метка: /
+>
+>Название: /sys/kernel/security
+>Тип: Ram
+>Объем диска: 0
+>Свободное пространство: 0
+>Метка: /sys/kernel/security
+>
+>Название: /dev/shm
+>Тип: Ram
+>Объем диска: 8254484480
+>Свободное пространство: 8211234816
+>Метка: /dev/shm
+>
+>Название: /sys/fs/cgroup
+>Тип: Ram
+>Объем диска: 0
+>Свободное пространство: 0
+>Метка: /sys/fs/cgroup
+>
+>Название: /sys/fs/pstore
+>Тип: Fixed
+>Объем диска: 0
+>Свободное пространство: 0
+>Метка: /sys/fs/pstore
+>
+>Название: /sys/fs/bpf
+>Тип: Fixed
+>Объем диска: 0
+>Свободное пространство: 0
+>Метка: /sys/fs/bpf
+>
+>Название: /proc/sys/fs/binfmt_misc
+>Тип: Ram
+>Объем диска: 0
+>Свободное пространство: 0
+>Метка: /proc/sys/fs/binfmt_misc
+>
+>Название: /dev/mqueue
+>Тип: Ram
+>Объем диска: 0
+>Свободное пространство: 0
+>Метка: /dev/mqueue
+>
+>Название: /dev/hugepages
+>Тип: Ram
+>Объем диска: 0
+>Свободное пространство: 0
+>Метка: /dev/hugepages
+>
+>Название: /sys/kernel/debug
+>Тип: Ram
+>Объем диска: 0
+>Свободное пространство: 0
+>Метка: /sys/kernel/debug
+>
+>Название: /sys/kernel/tracing
+>Тип: Fixed
+>Объем диска: 0
+>Свободное пространство: 0
+>Метка: /sys/kernel/tracing
+>
+>Название: /sys/fs/fuse/connections
+>Тип: Ram
+>Объем диска: 0
+>Свободное пространство: 0
+>Метка: /sys/fs/fuse/connections
+>
+>Название: /sys/kernel/config
+>Тип: Ram
+>Объем диска: 0
+>Свободное пространство: 0
+>Метка: /sys/kernel/config
+>
+>Название: /tmp
+>Тип: Ram
+>Объем диска: 8254484480
+>Свободное пространство: 8238710784
+>Метка: /tmp
+>
+>Название: /boot/efi
+>Тип: Fixed
+>Объем диска: 313942016
+>Свободное пространство: 302096384
+>Метка: /boot/efi
+>
+>Название: /var/lib/docker/overlay2/4a2802316a49f1a30573524d0059ce49f6cd6cfe40608b77efb6febce3b2d66d/>merged
+>Тип: Unknown
+>
+>Название: /var/lib/docker/overlay2/a91156ea1396c9882176076645afbc6b4c5ded5492d9226442a6c5b55d07fd67/>merged
+>Тип: Unknown
+>
+>Название: /var/lib/docker/overlay2/de7b5cae3507be9b60ff7a3ddb8375e6968f465e3d926eb1c56c0206c8e2e17a/>merged
+>Тип: Unknown
+>
+>Название: /var/lib/docker/overlay2/f6ab6ef9b79dabd97d12c898d4239d1fb173ebcd83a1abf5c22704e730390115/>merged
+>Тип: Unknown
+>
+>Название: /var/lib/docker/overlay2/cf4ba0731412147587fe830cbcc73005b3f660443a73da88977222e55c304e27/>merged
+>Тип: Unknown
+>
+>Название: /run/docker/netns/default
+>Тип: Unknown
+>
+>Название: /run/docker/netns/fe8e9119c528
+>Тип: Unknown
+>
+>Название: /run/user/1000
+>Тип: Ram
+>Объем диска: 1650896896
+>Свободное пространство: 1650798592
+>Метка: /run/user/1000
+>
+>Название: /run/user/1000/doc
+>Тип: Unknown
+>Unhandled exception. System.UnauthorizedAccessException: Access to the path is denied.
+> ---> System.IO.IOException: Operation not permitted
+>   --- End of inner exception stack trace ---
+>   at System.IO.DriveInfo.CheckStatfsResultAndThrowIfNecessary(Int32 result)
+>   at System.IO.DriveInfo.get_TotalSize()
+>   at Program.<Main>$(String[] args) in /home/kei/RiderProjects/OAP/stream/Program.cs:line 9
+>
+>Process finished with exit code 134.
+>```
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Многопоточность. Потоки, асинхронные вычисления](./t5_thread_async.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [Типы файлов: CSV, XML, JSON.](./t5_file_types.md)

+ 904 - 0
articles/t5_function.md

@@ -0,0 +1,904 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Объявление множества. Работа с датами. Кортежи.](./cs_misc_types.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [Делегаты, события и лямбды](./t5_delegate.md)
+
+# Общие сведения о подпрограммах. Определение и вызов подпрограмм. Область видимости и время жизни переменной. Механизм передачи параметров. 
+
+Содрано [отсюда](https://metanit.com/sharp/tutorial/2.8.php)
+
+* [Методы](#методы)
+* [Параметры методов](#параметры-методов)
+* [Передача параметров по ссылке и значению. Выходные параметры](#передача-параметров-по-ссылке-и-значению-выходные-параметры)
+* [Массив параметров и ключевое слово params](#массив-параметров-и-ключевое-слово-params)
+* [Область видимости (контекст) переменных](#область-видимости-контекст-переменных)
+* [Секреты хорошей функции](#секреты-хорошей-функции-копипаст-с-хабра)
+
+## Методы (функции, процедуры)
+
+Если переменные хранят некоторые значения, то **методы** содержат _набор операторов_, которые выполняют определенные действия. По сути метод - это именованный блок кода, который выполняет некоторые действия.
+
+### Общее определение метода
+
+Общее определение методов выглядит следующим образом:
+
+```
+[модификаторы] тип_возвращаемого_значения название_метода ([параметры])
+{
+    // тело метода
+}
+```
+
+Модификаторы и параметры необязательны.
+
+Например, по умолчанию консольная программа на языке **C#** должна содержать как минимум один метод - метод _Main_, который является точкой входа в приложение:
+
+```cs
+static void Main(string[] args)
+{
+     
+}
+```
+
+>В **.NET Core** консольная программа выглядит как скрипт, где точкой входа является первая выполняемая строка. Хотя несомненно где-то под капотом метод _Main_ есть 
+
+Ключевое слово **static** является _модификатором_. Далее идет _тип возвращаемого значения_. В данном случае ключевое слово **void** указывает на то, что метод ничего не возвращает.
+
+Далее идет название метода - _Main_ и в скобках параметры - `string[] args`. И в фигурные скобки заключено тело метода - все действия, которые он выполняет. В данном случае метод *Main* пуст, он не содержит никаких операторов и по сути ничего не выполняет.
+
+Определим еще пару методов:
+
+```cs
+// метод Main я убрал
+
+static void SayHello()
+{
+    Console.WriteLine("Hello");
+}
+static void SayGoodbye()
+{
+    Console.WriteLine("GoodBye");
+}
+```
+
+В данном случае определены два метода: *SayHello* и *SayGoodbye*. Оба метода имеют модификатор **static**, а в качестве возвращаемого типа для них определен тип **void**. То есть данные методы ничего не возвращают, просто производят некоторые действия. И также оба метода не имеют никаких параметров, поэтому после названия метода указаны пустые скобки.
+
+Оба метода выводят на консоль некоторую строку. Причем для вывода на консоль методы используют другой метод, который определен в .NET по умолчанию - `Console.WriteLine()`.
+
+Но если мы запустим даную программу, то мы не увидим никаких сообщений, которые должны выводить методы _SayHello_ и _SayGoodbye_. Потому что стартовой точкой является любой выполняемый код, кроме определения функций. При запуске программы выполняется только метод _Main_ и все операторы, которые составляют тело этого метода. Все остальные методы не выполняются.
+
+### Вызов методов
+
+Чтобы использовать методы _SayHello_ и _SayGoodbye_ в программе, нам надо их явно вызвать.
+
+Для вызова метода указывается его имя, после которого в скобках идут значения для его параметров (если метод принимает параметры).
+
+```
+название_метода (значения_для_параметров_метода);
+```
+
+Например, вызовем методы _SayHello_ и _SayGoodbye_:
+
+```cs
+SayHello();
+SayGoodbye();
+
+Console.ReadKey();
+
+static void SayHello()
+{
+    Console.WriteLine("Hello");
+}
+static void SayGoodbye()
+{
+    Console.WriteLine("GoodBye");
+}
+```
+
+Консольный вывод программы:
+
+```
+Hello
+GoodBye
+```
+
+Преимуществом методов является то, что их можно повторно и многократно вызывать в различных частях программы. Например, в примере выше в двух методах для вывода строки на консоль используется метод `Console.WriteLine`.
+
+### Возвращение значения
+
+Метод может возвращать значение, какой-либо результат. В примере выше были определены два метода, которые имели тип возвращаемого результата **void**. Методы с таким типом не возвращают никакого значения. Они просто выполняют некоторые действия.
+
+Если метод имеет любой другой тип, отличный от **void**, то такой метод обязан вернуть значение этого типа. Для этого применяется оператор **return**, после которого идет возвращаемое значение:
+
+```
+return возвращаемое значение;
+```
+
+Например, определим еще пару методов:
+
+```cs
+static string GetHello()
+{
+    return "Hello";
+}
+static int GetSum()
+{
+    int x = 2;
+    int y = 3;
+    int z = x + y;
+    return z;
+}
+```
+
+Метод _GetHello_ имеет тип **string**, следовательно, он должен возвратить строку. Поэтому в теле метода используется оператор **return**, после которого указана возвращаемая строка.
+
+Метод _GetSum_ имеет тип **int**, следовательно, он должен возвратить значение типа **int** - целое число. Поэтому в теле метода используется оператор **return**, после которого указано возвращаемое число (в данном случае результат суммы переменных _x_ и _y_).
+
+После оператора **return** также можно указывать сложные выражения, которые возвращают определенный результат. Например:
+
+```cs
+static int GetSum()
+{
+    int x = 2;
+    int y = 3;
+    return x + y;
+}
+```
+
+При этом методы, которые в качестве возвращаемого типа имеют любой тип, отличный от **void**, обязательно должны использовать оператор **return** для возвращения значения. Например, следующее определение метода некорректно:
+
+```cs
+static string GetHello()
+{
+    Console.WriteLine("Hello");
+}
+```
+
+Также между возвращаемым типом метода и возвращаемым значением после оператора **return** должно быть соответствие. Например, в следующем случае возвращаемый тип - **int**, но метод возвращает строку (тип **string**), поэтому такое определение метода некорректно:
+
+```cs
+static int GetSum()
+{
+    int x = 2;
+    int y = 3;
+    return "5"; // ошибка - надо возвращать число
+}
+```
+
+Результат методов, который возвращают значение, мы можем присвоить переменным или использовать иным образом в программе:
+
+```cs
+string message = GetHello();
+int sum = GetSum();
+
+Console.WriteLine(message);  // Hello
+Console.WriteLine(sum);     // 5
+
+Console.ReadKey();
+
+static string GetHello()
+{
+    return "Hello";
+}
+static int GetSum()
+{
+    int x = 2;
+    int y = 3;
+    return x + y;
+}
+```
+
+Метод _GetHello_ возвращает значение типа **string**. Поэтому мы можем присвоить это значение какой-нибудь переменной типа **string**: `string message = GetHello();`
+
+Второй метод - _GetSum_ - возвращает значение типа **int**, поэтому его можно присвоить переменной, которая принимает значение этого типа: `int sum = GetSum();`.
+
+### Выход из метода
+
+Оператор **return** не только возвращает значение, но и производит выход из метода. Поэтому он должен определяться после остальных инструкций. Например:
+
+```cs
+static string GetHello()
+{
+    return "Hello";
+    Console.WriteLine("After return");
+}
+```
+
+С точки зрения синтаксиса данный метод корректен, однако его инструкция `Console.WriteLine("After return")` не имеет смысла - она никогда не выполнится, так как до ее выполнения оператор **return** возвратит значение и произведет выход из метода.
+
+Однако мы можем использовать оператор **return** и в методах с типам **void**. В этом случае после оператора **return** не ставится никакого возвращаемого значения (ведь метод ничего не возвращает). Типичная ситуация - в зависимости от опеределенных условий произвести выход из метода:
+
+```cs
+static void SayHello()
+{
+    int hour = 23;
+    if(hour > 22)
+    {
+        return;
+    }
+    else
+    {
+        Console.WriteLine("Hello");
+    }
+}
+```
+
+### Сокращенная запись методов
+
+Если метод в качестве тела определяет только одну инструкцию, то мы можем сократить определение метода. Например, допустим у нас есть метод:
+
+```cs
+static void SayHello()
+{
+    Console.WriteLine("Hello");
+}
+```
+
+Мы можемего сократить следующим образом:
+
+```cs
+static void SayHello() => Console.WriteLine("Hello");
+```
+
+То есть после списка параметров ставится знак равно и больше чем, после которого идет выполняемая инструкция.
+
+Подобным образом мы можем сокращать методы, которые возвращают значение:
+
+```cs
+static string GetHello()
+{
+    return "hello";
+}
+```
+
+Анлогичен следующему методу:
+
+```cs
+static string GetHello() => "hello";
+```
+
+## Параметры методов
+
+Параметры позволяют передать в метод некоторые входные данные. Например, определеим метод, который складывает два числа:
+
+```cs
+static int Sum(int x, int y)
+{
+    return x + y;
+}
+```
+
+Метод _Sum_ имеет два параметра: _x_ и _y_. Оба параметра представляют тип **int**. Поэтому при вызове данного метода нам обязательно надо передать на место этих параметров два числа.
+
+```cs
+int result = Sum(10, 15);
+Console.WriteLine(result);  // 25
+    
+Console.ReadKey();
+
+static int Sum(int x, int y)
+{
+    return x + y;
+}
+```
+
+При вызове метода _Sum_ значения передаются параметрам по позиции. Например, в вызове `Sum(10, 15)` число `10` передается параметру _x_, а число `15` - параметру _y_. Значения, которые передаются параметрам, еще называются аргументами. То есть передаваемые числа `10` и `15` в данном случае являются аргументами.
+
+Иногда можно встретить такие определения как формальные параметры и фактические параметры. Формальные параметры - это собственно параметры метода (в данном случае _x_ и _y_), а фактические параметры - значения, которые передаются формальным параметрам. То есть фактические параметры - это и есть аргументы метода.
+
+Передаваемые параметру значения могут представлять значения переменных или результат работы сложных выражений, которые возвращают некоторое значение:
+
+```cs
+int a = 25;
+int b = 35;
+int result = Sum(a, b);
+Console.WriteLine(result);  // 60
+
+result = Sum(b, 45);
+Console.WriteLine(result);  // 80
+
+// "a + b + 12" представляет значение параметра x
+result = Sum(a + b + 12, 18);
+
+Console.WriteLine(result);  // 90
+
+Console.ReadKey();
+
+static int Sum(int x, int y)
+{
+    return x + y;
+}
+```
+
+Если параметрами метода передаются значения переменных, которые представляют базовые примитивные типы (за исключением типа **object**), то таким переменным должно быть присвоено значение. Например, следующая программа не скомпилируется:
+
+```cs
+int a;
+int b = 9;
+
+// Ошибка - переменной a не присвоено значение
+Sum(a, b);
+
+Console.ReadKey();
+
+static int Sum(int x, int y)
+{
+    return x + y;
+}
+```
+
+При передаче значений параметрам важно учитывать тип параметров: между аргументами и параметрами должно быть соответствие по типу. Например:
+
+```cs
+// Name: Tom  Age: 24
+Display("Tom", 24); 
+
+Console.ReadKey();
+
+static void Display(string name, int age)
+{
+    Console.WriteLine($"Name: {name}  Age: {age}");
+}
+```
+
+В данном случае первый параметр метода _Display_ представляет тип **string**, поэтому мы должны передать этому параметру значение типа **string**, то есть строку. Второй параметр представляет тип **int**, поэтому должны передать ему целое число, которое соответствует типу **int**.
+
+Другие данные параметрам мы передать не можем. Например, следующий вызов метода _Display_ будет ошибочным:
+
+```cs
+// Ошибка! несоответствие значений типам параметров
+Display(45, "Bob"); 
+```
+
+### Необязательные параметры
+
+По умолчанию при вызове метода необходимо предоставить значения для всех его параметров. Но **C#** также позволяет использовать необязательные параметры. Для таких параметров нам необходимо объявить значение по умолчанию. Также следует учитывать, что после необязательных параметров все последующие параметры также должны быть необязательными:
+
+```cs
+static int OptionalParam(
+    int x, int y, int z=5, int s=4)
+{
+    return x + y + z + s;
+}
+```
+
+Так как последние два параметра объявлены как необязательные, то мы можем один из них или оба опустить:
+
+```cs
+OptionalParam(2, 3);
+
+OptionalParam(2, 3, 10);
+
+Console.ReadKey();
+```
+
+### Именованные параметры
+
+В предыдущих примерах при вызове методов значения для параметров передавались в порядке объявления этих параметров в методе. Но мы можем нарушить подобный порядок, используя именованные параметры:
+
+```cs
+static int OptionalParam(int x, int y, int z=5, int s=4)
+{
+    return x + y + z + s;
+}
+
+OptionalParam(x:2, y:3);
+    
+//Необязательный параметр z использует значение по умолчанию
+OptionalParam(y:2, x:3, s:10);
+
+Console.ReadKey();
+```
+
+## Передача параметров по ссылке и значению. Выходные параметры
+
+Существует два способа передачи параметров в метод в языке C#: **по значению** и **по ссылке**.
+
+### Передача параметров по значению
+
+Наиболее простой способ передачи параметров представляет передача по значению, по сути это обычный способ передачи параметров:
+
+```cs
+// параметры передаются по значению
+Sum(10, 15);
+
+Console.ReadKey();
+
+static int Sum(int x, int y)
+{
+    return x + y;
+}
+```
+
+### Передача параметров по ссылке и модификатор ref
+
+При передаче параметров по ссылке перед параметрами используется модификатор **ref**:
+
+```cs
+int x = 10;
+int y = 15;
+Addition(ref x, y); // вызов метода
+Console.WriteLine(x);   // 25
+
+Console.ReadLine();
+
+// параметр x передается по ссылке
+static void Addition(ref int x, int y)
+{
+    x += y;
+}
+```
+
+Обратите внимание, что модификатор **ref** указывается, как при объявлении метода, так и при его вызове в методе _Main_.
+
+### Сравнение передачи по значению и по ссылке
+
+В чем отличие двух способов передачи параметров? При передаче по значению метод получает не саму переменную, а ее копию. А при передаче параметра по ссылке метод получает адрес переменной в памяти. И, таким образом, если в методе изменяется значение параметра, передаваемого по ссылке, то также изменяется и значение переменной, которая передается на его место.
+
+Рассмотрим два аналогичных примера. Первый пример - передача параметра по значению:
+
+```cs
+int a = 5;
+Console.WriteLine(
+    $"Начальное значение переменной a = {a}");
+
+// Передача переменных по значению
+// После выполнения этого кода по-прежнему a = 5, 
+// так как мы передали лишь ее копию
+IncrementVal(a);
+
+Console.WriteLine(
+    $"Переменная a после передачи по значению равна = {a}");
+
+Console.ReadKey();
+
+// передача по значению
+static void IncrementVal(int x)
+{
+    x++;
+    Console.WriteLine($"IncrementVal: {x}");
+}
+```
+
+Консольный вывод:
+
+```
+Начальное значение переменной a = 5
+IncrementVal: 6
+Переменная a после передачи по значению равна = 5
+```
+
+При вызове метод *IncrementVal* получает копию переменной _a_ и увеличивает значение этой копии. Поэтому в самом методе *IncrementVal* мы видим, что значение параметра _x_ увеличилось на `1`, но после выполнения метода переменная _a_ имеет прежнее значение - `5`. То есть изменяется копия, а сама переменная не изменяется.
+
+Второй пример - аналогичный метод с передачей параметра по ссылке:
+
+```cs
+int a = 5;
+
+Console.WriteLine(
+    $"Начальное значение переменной a  = {a}");
+
+// Передача переменных по ссылке
+// После выполнения этого кода a = 6, 
+// так как мы передали саму переменную
+IncrementRef(ref a);
+
+Console.WriteLine(
+    $"Переменная a после передачи ссылке равна = {a}");
+    
+Console.ReadKey();
+
+// передача по ссылке
+static void IncrementRef(ref int x)
+{
+    x++;
+    Console.WriteLine($"IncrementRef: {x}");
+}
+```
+
+Консольный вывод:
+
+```
+Начальное значение переменной a = 5
+IncrementRef: 6
+Переменная a после передачи по ссылке равна = 6
+```
+
+В метод *IncrementRef* передается ссылка на саму переменную `a` в памяти. И если значение параметра в *IncrementRef* изменяется, то это приводит и к изменению переменной `a`, так как и параметр и переменная указывают на один и тот же адрес в памяти.
+
+### Выходные параметры. Модификатор out
+
+>Сейчас не используется. Раньше нужен был для возврата из функции нескольких результатов (часто результат функции использовался как статус выполнения успешно|неуспешно), но сейчас вместо статусов используются исключения, а для возврата нескольких значений можно использовать кортежи.
+
+Выше мы использовали входные параметры. Но параметры могут быть также выходными. Чтобы сделать параметр выходным, перед ним ставится модификатор **out**:
+
+```cs
+static void Sum(int x, int y, out int a)
+{
+    a = x + y;
+}
+```
+
+Здесь результат возвращается не через оператор **return**, а через выходной параметр. Использование в программе:
+
+```cs
+int x = 10;
+    
+int z;
+    
+Sum(x, 15, out z);
+    
+Console.WriteLine(z);
+
+Console.ReadKey();
+```
+
+Причем, как и в случае с *ref* ключевое слово **out** используется как при определении метода, так и при его вызове.
+
+Также обратите внимание, что методы, использующие такие параметры, обязательно должны присваивать им определенное значение. То есть следующий код будет недопустим, так как в нем для **out**-параметра не указано никакого значения:
+
+```cs
+static void Sum(int x, int y, out int a)
+{
+    Console.WriteLine(x+y);
+}
+```
+
+Смысл использования подобных параметров состоит в том, что по сути мы можем вернуть из метода не один вариант, а несколько. Например:
+
+```cs
+int x = 10;
+int area;
+int perimetr;
+GetData(x, 15, out area, out perimetr);
+Console.WriteLine("Площадь : " + area);
+Console.WriteLine("Периметр : " + perimetr);
+
+Console.ReadKey();
+
+static void GetData(
+    int x, int y, out int area, out int perim)
+{
+    area= x * y;
+    perim= (x + y)*2; 
+}
+```
+
+Здесь у нас есть метод _GetData_, который, допустим, принимает стороны прямоугольника. А два выходных параметра мы используем для подсчета площади и периметра прямоугольника.
+
+По сути, как и в случае с ключевым словом **ref**, ключевое слово **out** применяется для передачи аргументов по ссылке. Однако в отличие от **ref** для переменных, которые передаются с ключевым словам **out**, не требуется инициализация. И кроме того, вызываемый метод должен обязательно присвоить им значение.
+
+>С появлением кортежей особого смысла в **out** параметрах нет, проще вернуть сколько угодно результатов завернув их в кортеж:
+
+```cs
+static (int area, int perim) GetData(int x, int y) {
+   return (x*x, (x + y)*2)    
+}
+``` 
+
+## Массив параметров и ключевое слово params
+
+Во всех предыдущих примерах мы использовали постоянное число параметров. Но, используя ключевое слово **params**, мы можем передавать неопределенное количество однотипных параметров:
+
+```cs
+static void Addition(params int[] integers)
+{
+    int result = 0;
+    for (int i = 0; i < integers.Length; i++)
+    {
+        result += integers[i];
+    }
+    Console.WriteLine(result);
+}
+ 
+Addition(1, 2, 3, 4, 5);
+    
+int[] array = new int[] { 1, 2, 3, 4 };
+Addition(array);
+
+Addition();
+Console.ReadLine();
+```
+
+Сам параметр с ключевым словом **params** при определении метода должен представлять одномерный массив того типа, данные которого мы собираемся использовать. При вызове метода на место параметра с модификатором **params** мы можем передать как отдельные значения, так и массив значений, либо вообще не передавать параметры.
+
+Если же нам надо передать какие- то другие параметры, то они должны указываться **до** параметра с ключевым словом **params**:
+
+```cs
+//Так работает
+static void Addition( 
+    int x, string mes, params int[] integers)
+{
+
+}
+```
+
+Вызов подобного метода:
+
+```cs
+Addition(2, "hello", 1, 3, 4);
+```
+
+Однако после параметра с модификатором **params** мы **не можем** указывать другие параметры. То есть следующее определение метода недопустимо:
+
+```cs
+//Так НЕ работает
+static void Addition(
+    params int[] integers, int x, string mes)
+{
+
+}
+```
+
+### Массив в качестве параметра
+
+Также этот способ передачи параметров надо отличать от передачи массива в качестве параметра:
+
+```cs
+// передача параметра с params
+static void Addition(params int[] integers)
+{
+    int result = 0;
+    for (int i = 0; i < integers.Length; i++)
+    {
+        result += integers[i];
+    }
+    Console.WriteLine(result);
+}
+
+// передача массива
+static void AdditionMas(int[] integers, int k)
+{
+    int result = 0;
+    for (int i = 0; i < integers.Length; i++)
+    {
+        result += (integers[i]*k);
+    }
+    Console.WriteLine(result);
+}
+ 
+Addition(1, 2, 3, 4, 5);
+
+int[] array = new int[] { 1, 2, 3, 4 };
+AdditionMas(array, 2);
+
+Console.ReadLine();
+```
+
+Так как метод _AdditionMas_ принимает в качестве параметра массив без ключевого слова **params**, то при его вызове нам обязательно надо передать в качестве параметра массив.
+
+## Область видимости (контекст) переменных
+
+Каждая переменная доступна в рамках определенного контекста или области видимости. Вне этого контекста переменная уже не существует.
+
+Существуют различные контексты:
+
+**Контекст класса**. Переменные, определенные на уровне класса, доступны в любом методе этого класса
+
+**Контекст метода**. Переменные, определенные на уровне метода, являются локальными и доступны только в рамках данного метода. В других методах они недоступны
+
+**Контекст блока кода**. Переменные, определенные на уровне блока кода, также являются локальными и доступны только в рамках данного блока. Вне своего блока кода они не доступны.
+
+Например, пусть класс **Program** определен следующим образом:
+
+```cs
+class Program // начало контекста класса
+{
+    static int a = 9; // переменная уровня класса
+     
+    static void Main(string[] args) // начало контекста метода Main
+    {
+        int b = a - 1; // переменная уровня метода
+ 
+        if (b > 0) { // начало контекста блока кода
+             
+            int c = b - 1; // переменная уровня блока кода
+ 
+        }  // конец контекста блока кода, переменная с уничтожается
+ 
+        // так нельзя, переменная c определена в блоке кода
+        // Console.WriteLine(c);
+ 
+        // так нельзя, переменная d определена в другом методе
+        // Console.WriteLine(d);
+ 
+        Console.Read();
+ 
+    } // конец контекста метода Main, переменная b уничтожается
+ 
+    void Display() // начало контекста метода Display
+    {
+        // переменная a определена в контексте класса, поэтому доступна
+        int d = a + 1;
+ 
+    } // конец конекста метода Display, переменная d уничтожается
+ 
+} // конец контекста класса, переменная a уничтожается
+```
+
+Здесь определенно четыре переменных: `a`, `b`, `c`, `d`. Каждая из них существует в своем контексте. Переменная `a` существует в контексте всего класса **Program** и доступна в любом месте и блоке кода в методах _Main_ и _Display_.
+
+Переменная `b` существует только в рамках метода _Main_. Также как и переменная `d` существует в рамках метода _Display_. В методе _Main_ мы не можем обратиться к переменной `d`, так как она в другом контексте.
+
+Переменная `c` существует только в блоке кода, границами которого являются открывающая и закрывающая фигурные скобки. Вне его границ переменная `c` не существует и к ней нельзя обратиться.
+
+Нередко границы различных контекстов можно ассоциировать с открывающимися и закрывающимися фигурными скобками, как в данном случае, которые задают пределы блока кода, метода, класса.
+
+При работе с переменными надо учитывать, что локальные переменные, определенные в методе или в блоке кода, скрывают переменные уровня класса, если их имена совпадают:
+
+```cs
+class Program
+{
+    static int a = 9; // переменная уровня класса
+     
+    static void Main(string[] args)
+    {
+        int a = 5; // скрывает переменную a, которая объявлена на уровне класса
+        Console.WriteLine(a); // 5
+    }
+}
+```
+
+При объявлении переменных также надо учитывать, что в одном контексте нельзя определить несколько переменных с одним и тем же именем.
+
+## Секреты хорошей функции (копипаст с хабра)
+
+Что отличает «хорошую» функцию от посредственной? Вы удивитесь, как много трактовок допускает слово «хорошая». В рамках этой статьи я буду считать функцию «хорошей», если она удовлетворяет большинству пунктов из следующего списка (выполнить все пункты для конкретной функции порой невозможно):
+
+* Она внятно названа
+* Соответствует принципу единственной ответственности
+* Содержит xml-комментарий
+* Возвращает значение
+* Состоит не более чем из 50 строк
+* Она идемпотентная и, если это возможно, чистая
+
+### Единственная ответственность
+
+Согласно этому принципу у функции должна быть единственная ответственность. То есть, она должна делать одну и только одну вещь. Один из самых веских доводов в пользу этого: если функция делает всего одну вещь, то и переписывать её придется в единственном случае: если эту самую вещь придется делать по-новому. Также становится ясно, когда функцию можно удалить, если, внеся изменения где-то в другом месте, мы поймем, что единственная обязанность функции более не актуальна, то мы от нее просто избавимся.
+
+Здесь лучше привести пример. Вот функция, делающая более одной «вещи»:
+
+```py
+def calculate_and print_stats(list_of_numbers):
+    sum = sum(list_of_numbers)
+    mean = statistics.mean(list_of_numbers)
+    median = statistics.median(list_of_numbers)
+    mode = statistics.mode(list_of_numbers)
+
+    print('-----------------Stats-----------------')
+    print('SUM: {}'.format(sum)
+    print('MEAN: {}'.format(mean)
+    print('MEDIAN: {}'.format(median)
+    print('MODE: {}'.format(mode)
+```
+
+А именно две: вычисляет набор статистических данных о списке чисел и выводит их в STDOUT. Функция нарушает правило: должна быть единственная конкретная причина, по которой ее, возможно, потребовалось бы изменить. В данном случае просматриваются две очевидные причины, по которым это понадобится: либо потребуется вычислять новую или иную статистику, либо потребуется изменить формат вывода. Поэтому данную функцию лучше переписать в виде двух отдельных функций: одна будет выполнять вычисления и возвращать их результаты, а другая – принимать эти результаты и выводить их в консоль. Функцию (вернее, наличие у нее двух обязанностей) с потрохами выдает слово and в ее названии.
+
+Такое разделение также серьезно упрощает тестирование функции, а еще позволяет не только разбить ее на две функции в рамках одного и того же модуля, но даже разнести две эти функции в совершенно разные модули, если это уместно. Это дополнительно способствует более чистому тестированию и упрощает поддержку кода.
+
+На самом деле, функции, выполняющие ровно две вещи, встречаются редко. Гораздо чаще натыкаешься на функции, делающие намного, намного больше операций. Опять же, из соображений удобочитаемости и тестируемости такие «многостаночные» функции следует дробить на однозадачные, в каждой из которых заключен единственный аспект работы.
+
+### XML-комментарии
+
+* Для каждой функции нужен комментарий
+* В нём следует соблюдать грамматику и пунктуацию; писать законченными предложениями
+* Комментарий начинается с краткого (в одно предложение) описания того, что делает функция
+* Комментарий формулируется в предписывающем, а не в описательном стиле
+
+Все эти пункты легко соблюсти, когда пишешь функции. Просто написание комментарием должно войти в привычку, причем, старайтесь писать их прежде, чем приступать к коду самой функции. Если у вас не получается написать четкий комментарий, характеризующую функцию – это хороший повод задуматься, зачем вы вообще пишете эту функцию.
+
+### Возвращаемые значения
+
+Функции можно (и следует) трактовать как маленькие самодостаточные программы. Они принимают некоторый ввод в форме параметров и возвращают результат. Параметры, конечно, опциональны. А вот возвращаемые значения обязательны с точки зрения внутреннего устройства. Если вы даже попытаетесь написать функцию, которая не возвращает значения – не сможете. Если функция даже не станет возвращать значения, то по-умолчанию вернётся *void*.
+
+Каждая функция должна возвращать полезное значение, хотя бы ради тестируемости. Код, который я пишу, должен быть протестирован (это не обсуждается). Кроме того, возвращая значение, мы можем выполнять сцепление методов и, следовательно, писать код вот так:
+
+```py
+with open('foo.txt', 'r') as input_file:
+    for line in input_file:
+        if line.strip().lower().endswith('cat'):
+            # ... делаем с этими строками что-нибудь полезное
+```
+
+Строка `if line.strip().lower().endswith('cat'):` работает, поскольку каждый из строковых методов (strip(), lower(), endswith()) в результате вызова функции возвращает строку.
+
+Вот несколько распространенных доводов, которые вам может привести программист, объясняя, почему написанная им функция не возвращает значения:
+
+> «Она всего лишь [какая-то операция, связанная с вводом/выводом, например, сохранение значения в базе данных]. Здесь я не могу вернуть ничего полезного.»
+
+Не соглашусь. Функция может вернуть True, если операция завершилась успешно.
+
+> «Здесь мы изменяем один из имеющихся параметров, используем его как ссылочный параметр.»
+
+Здесь – два замечания. Во-первых, всеми силами старайтесь так не делать. Во-вторых, снабжать функцию каким-либо аргументом лишь для того, чтобы узнать, что она изменилась – в лучшем случае удивительно, а в худшем – попросту опасно. Вместо этого, как и при работе со строковыми методами, старайтесь возвращать новый экземпляр параметра, в котором уже отражены примененные к нему изменения. Даже если это не получается делать, поскольку создание копии какого-то параметра сопряжено с чрезмерными издержками, все равно можно откатываться к предложенному выше варианту «Вернуть True, если операция завершилась успешно».
+
+> «Мне нужно возвращать несколько значений. Нет такого единственного значения,которое в данном случае было бы целесообразно возвращать.»
+
+Этот аргумент немного надуманный, но мне доводилось его слышать. Ответ, разумеется, как раз в том, что автор и хотел сделать – но не знал как: для возврата нескольких значений используйте кортеж.
+
+Наконец, самый сильный аргумент в пользу того, что полезное значение лучше возвращать в любом случае – в том, что вызывающая сторона всегда может с полным правом эти значения игнорировать. Короче говоря, возврат значения от функции – практически наверняка здравая идея, и крайне маловероятно, что мы таким образом что-нибудь повредим, даже в сложившихся базах кода.
+
+### Длина функции
+
+Я не раз признавался, что довольно туп. Могу одновременно держать в голове примерно три вещи. Если вы дадите мне прочесть 200-строчную функцию и спросите, что она делает, я, вероятно, буду таращиться на нее не менее 10 секунд. Длина функции прямо сказывается на ее удобочитаемости и, следовательно, на поддержке. Поэтому старайтесь, чтобы ваши функции оставались короткими. 50 строк – величина, взятая совершенно с потолка, но мне она кажется разумной. (Надеюсь), что большинство функций, которые вам доведется писать, будут значительно короче.
+
+Если функция соответствует *Принципу единственной ответственности*, то, вероятно, она будет достаточно краткой. Если она чистая или идемпотентная (об этом мы поговорим) ниже – то, наверное, она также получится короткой. Все эти идеи гармонично сочетаются друг с другом и помогают писать хороший, чистый код.
+
+Итак, что же делать, если ваша функция получилась слишком длинной? РЕФАКТОРИТЬ! Вероятно, вам приходится заниматься рефакторингом постоянно, даже если вы не знаете этого термина. Рефакторинг – это попросту изменение структуры программы, без изменения ее поведения. Поэтому, извлечение нескольких строк кода из длинной функции и превращение их в самостоятельную функцию – это один из типов рефакторинга. Оказывается, это еще и наиболее распространенный, и самый быстрый способ продуктивного укорачивания длинных функций. Поскольку вы даете этим новым функциям подходящие имена, получающийся у вас код гораздо проще читать. Я написал целую книгу о рефакторинге (на самом деле, я им постоянно занимаюсь), так что здесь вдаваться в детали не буду. Просто знайте, что, если у вас есть слишком длинная функция – то ее следует рефакторить.
+
+### Идемпотентность и функциональная чистота
+
+Заголовок этого раздела может показаться слегка устрашающим, но концептуально раздел прост. Идемпотентная функция при одинаковом наборе аргументов всегда возвращает одно и то же значение, независимо от того, сколько раз ее вызывают. Результат не зависит от нелокальных переменных, изменяемости аргументов или от любых данных, поступающих из потоков ввода/вывода. Следующая функция add_three(number) идемпотентна:
+
+```cs
+/// вернуть *число* + 3
+function int addThree(int number) {
+    return number + 3;
+}
+```
+
+Независимо от того, сколько раз мы вызовем `addThree(7)`, ответ всегда будет равен `10`. А вот другой случай – функция, не являющаяся идемпотентной:
+
+```cs
+/// Вернуть 3 + число, введенное пользователем
+function int addThree() {
+    Console.WriteLine('Enter a number: ');
+    int number = Convert.ToInt32( Console.ReadLine() );
+    return number + 3;
+}
+```
+
+Эта, откровенно надуманная, функция не идемпотентна, поскольку возвращаемое значение функции зависит от ввода/вывода, а именно – от числа, введенного пользователем. Разумеется, при разных вызовах `addThree()` возвращаемые значения будут отличаться. Если мы дважды вызовем эту функцию, то пользователь в первом случае может ввести `3`, а во втором – `7`, и тогда два вызова `addThree()` вернут `6` и `10` соответственно.
+
+Вне программирования также встречаются примеры идемпотентности – например, по такому принципу устроена кнопка «вверх» у лифта. Нажимая ее в первый раз,мы «уведомляем» лифт, что хотим подняться. Поскольку кнопка идемпотентна, то сколько ее потом ни нажимать – ничего страшного не произойдет. Результат будет всегда одинаков.
+
+### Почему идемпотентность так важна
+
+Тестируемость и удобство в поддержке. Идемпотентные функции легко тестировать, поскольку они гарантированно, в любом случае вернут одинаковый результат, если вызвать их с одними и теми же аргументами. Тестирование сводится к проверке того, что при разнообразных вызовах функция всегда возвращает ожидаемое значение. Более того, эти тесты будут быстрыми: скорость тестов – важная проблема, которую часто обходят вниманием при модульном тестировании. А рефакторинг при работе с идемпотентными функциями – вообще легкая прогулка. Не важно, как вы измените код вне функции – результат ее вызова с одними и теми же аргументами всегда будет один и тот же.
+
+### Что такое «чистая» функция?
+
+В функциональном программировании функция считается чистой, если она, во-первых, идемпотентна, а во-вторых – не вызывает наблюдаемых побочных эффектов. Не забывайте: функция идемпотентна, если всегда возвращает один и тот же результат при конкретном наборе аргументов. Однако, это не означает, что функция не может влиять на другие компоненты – например, на нелокальные переменные или потоки ввода/вывода. Например, если бы идемпотентная версия вышеприведенной функции _addThree_ выводила результат в консоль, а лишь затем возвращала бы его, она все равно считалась бы идемпотентной, поскольку при ее обращении к потоку ввода/вывода эта операция доступа никак не влияет на значение, возвращаемое от функции. Вызов _Console.WriteLine_ – это просто побочный эффект: взаимодействие с остальной программой или системой как таковой, происходящее наряду с возвратом значения.
+
+Давайте немного разовьем наш пример с _addThree_. Можно написать следующий код, чтобы определить, сколько раз была вызвана _addThree_:
+
+```cs
+var addThreeCalls = 0;
+
+/// Вернуть *число* + 3
+function int addThree(int number) {
+    Console.WriteLine("Returning {0}", number + 3);
+    addThreeCalls += 1;
+    return number + 3;
+}
+
+/// Вернуть, сколько раз была вызвана *addThree*
+function void numCalls() {
+    return addThreeCalls;
+}
+```
+
+Теперь мы выполняем вывод в консоль (это побочный эффект) и изменяем нелокальную переменную (другой побочный эффект), но, поскольку ни то, ни другое не влияет на значение, возвращаемое функцией, она все равно идемпотентна.
+
+Чистая функция не должна оказывать побочных эффектов. Она не только не использует никаких «внешних данных» при расчете значения, но и не взаимодействует с остальной программой/системой, только вычисляет и возвращает указанное значение. Следовательно, хотя наше новое определение _addThree_ остается идемпотентным, эта функция уже не чистая.
+
+В чистых функциях нет инструкций логирования или вызовов _WriteLine_. При работе они не обращаются к базе данных и не используют соединений с интернетом. Не обращаются к нелокальным переменным и не изменяют их. И не вызывают других не-чистых функций.
+
+Короче говоря, они не оказывают «жуткого дальнодействия», выражаясь словами Эйнштейна (но в контексте информатики, а не физики). Они не изменяют каким-либо образом остальные части программы или системы. В императивном программировании (а именно им вы и занимаетесь, когда пишете код), такие функции – самые безопасные. Они известны своей тестируемостью и удобством в поддержке; более того, поскольку они идемпотентны, тестирование таких функций гарантированно будет столь же быстрым, как и выполнение. Сами тесты также просты: не приходится подключаться к базе данных либо имитировать какие-либо внешние ресурсы, готовить стартовую конфигурацию кода, а по окончании работы не нужно ничего подчищать.
+
+Честно говоря, идемпотентность и чистота очень желательны, но не обязательны. То есть, нам бы хотелось писать только чистые или идемпотентные функции, учитывая все вышеупомянутые их преимущества, но это не всегда возможно. Суть, однако, в том, чтобы приучиться писать код, естественным образом не допуская побочных эффектов и внешних зависимостей. Таким образом, каждую написанную нами строку кода станет проще тестировать, даже если не удастся обойтись только лишь чистыми или идемпотентными функциями.
+
+---
+
+## Контрольные вопросы
+
+1. [Общее определение метода](#общее-определение-метода)
+1. [Сокращенная запись методов](#сокращенная-запись-методов)
+1. [Необязательные параметры](#необязательные-параметры)
+1. [Именованные параметры](#именованные-параметры)
+1. [Массив параметров и ключевое слово params](#массив-параметров-и-ключевое-слово-params)
+1. [Область видимости переменных](#область-видимости-контекст-переменных)
+1. [Секреты хорошей функции](#секреты-хорошей-функции-копипаст-с-хабра)
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Объявление множества. Работа с датами. Кортежи.](./cs_misc_types.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [Делегаты, события и лямбды](./t5_delegate.md)

+ 379 - 0
articles/t5_regex.md

@@ -0,0 +1,379 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Типы файлов: CSV, XML, JSON.](./t5_file_types.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [ООП. Базовые понятия.](./t6_oop1.md)
+
+# Регулярные выражения
+
+<!-- https://habr.com/ru/post/349860/ -->
+
+<!-- 
+https://github.com/kolei/OAP/blob/c2dc3a2c5c5c9d677f34030c4632a484ed94a568/articles/t3l6.md
+
+старый вариант
+ -->
+
+**Регулярное выражение** — это строка, задающая шаблон поиска подстрок в тексте. Одному шаблону может соответствовать много разных строчек. 
+
+>Термин «Регулярные выражения» является переводом английского словосочетания «Regular expressions». 
+
+Регулярное выражение, или коротко «регулярка», состоит из обычных символов и специальных командных последовательностей. 
+
+Например, **`\d`** задаёт любую цифру, а **`\d+`** — задает любую последовательность из одной или более цифр. 
+
+Работа с регулярками реализована во всех современных языках программирования. 
+
+Однако существует несколько «диалектов», поэтому функционал регулярных выражений может различаться от языка к языку (реализация регулярных выражений в C# совместима с PERL).
+
+## Примеры регулярных выражений
+
+Регулярка    | Её смысл
+:-----------:|----------
+simple text	 | В точности текст «simple text»
+`\d{5}`    | Последовательности из 5 цифр<br/>`\d` - означает любую цифру<br/>`{5}` - ровно 5 раз
+`\d\d/\d\d/\d{4}`	| Даты в формате ДД/ММ/ГГГГ<br/>(и прочие строки, на них похожие, например: 98/76/5432)
+`\b\w{3}\b` | Слова в точности из трёх букв<br/>`\b` означает границу слова (с одной стороны буква, а с другой — нет)<br/>`\w` - любая буква,<br/>`{3}` - ровно три раза
+`[-+]?\d+` |	Целое число, например, 7, +17, -42, 0013 (возможны ведущие нули)<br/>`[-+]?` - либо "-", либо "+", либо пусто<br/>`\d+` - последовательность из 1 или более цифр
+`[-+]?(?:\d+(?:\.\d*)?`\|`\.\d+)(?:[eE][-+]?\d+)?` |	Действительное число, возможно в экспоненциальной записи<br/>Например, 0.2, +5.45, -.4, 6e23, -3.17E-14.
+
+## Сила и ответственность
+
+Регулярные выражения — это очень мощный инструмент. 
+
+Но использовать их следует с умом и осторожностью, и только там, где они действительно приносят пользу, а не вред. 
+
+**Во-первых**, плохо написанные регулярные выражения работают медленно. 
+
+**Во-вторых**, их зачастую очень сложно читать, особенно если регулярка написана не лично тобой пять минут назад. 
+
+**В-третьих**, очень часто даже небольшое изменение задачи (того, что требуется найти) приводит к значительному изменению выражения. 
+
+Поэтому про регулярки часто говорят, что это *write only code* (код, который только пишут с нуля, но не читают и не правят).
+
+Вот пример write-only регулярки (для проверки валидности e-mail адреса (не надо так делать!!!)):
+
+```regexp
+(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|
+2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])
+```
+
+А вот [здесь](http://www.ex-parrot.com/~pdw/Mail-RFC822-Address.html) более точная регулярка для проверки корректности email адреса стандарту RFC822. Если вдруг будете проверять email, то не делайте так! Если адрес вводит пользователь, то пусть вводит почти что угодно, лишь бы там были собака и точка. Надёжнее всего отправить туда письмо и убедиться, что пользователь может его получить.
+
+## Документация и ссылки
+
+* Оригинальная документация: https://docs.python.org/3/library/re.html;
+* Очень подробный и обстоятельный материал: https://www.regular-expressions.info/;
+* Разные сложные трюки и тонкости с примерами: http://www.rexegg.com/;
+* Он-лайн отладка регулярок https://regex101.com;
+* Он-лайн визуализация регулярок https://www.debuggex.com/
+
+## Основы синтаксиса
+
+Любая строка (в которой нет сециальных символов: `.^$*+?{}[]\|()`) сама по себе является регулярным выражением. Так, выражению "Хаха" будет соответствовать строка “Хаха” и только она. Регулярные выражения являются **регистрозависимыми**, поэтому строка “хаха” (с маленькой буквы) уже не будет соответствовать выражению выше. 
+
+Регулярные выражения имеют спецсимволы `.^$*+?{}[]\|()`, которые в регулярках являются управляющими конструкциями. Для написания их просто как символов требуется их экранировать, для чего нужно поставить перед ними знак "**`\`**". Так же, как и везде, в регулярных выражения выражение **`\n`** соответствует концу строки, а **`\t`** — табуляции. 
+
+### Шаблоны, соответствующие одному символу
+
+Во всех примерах ниже соответствия регулярному выражению выделяются курсивом.
+
+Шаблон | Описание | Пример | Применяем к тексту
+:-----:|----------|:------:|-------------------
+**.**	   | Один любой символ, кроме новой строки **`\n`** | м.л.ко | <u>м***о***л***о***ко</u><br/><u>м**а**л**а**ко</u><br/><u>м**0**л**0**ко</u>
+**`\d`** | Любая цифра (Вообще говоря, в **`\d`** включается всё, что в **юникоде** помечено как «цифра») | СУ\d\d | <u>СУ**35**</u><br/><u>СУ**11**</u>1<br/>АЛ<u>СУ**14**</u>
+**`\D`** | Любой символ, кроме цифры | `926\D123` | <u>926<b>)</b>123</u><br/>1<u>926<b>-</b>123</u>4
+**`\s`** | Любой пробельный символ (пробел, табуляция, конец строки и т.п.) | `бор\sода` | <u>бор ода</u><br/>"<u>бор<br/>ода</u>"
+**`\S`** | Любой непробельный символ | `\S123` | <u><b>X</b>123</u><br/><u><b>я</b>123</u><br/><u><b>!</b>123</u>456
+**`\w`** | Любая буква (то, что может быть частью слова), а также цифры и знак "**_**" | `\w\w\w` | <u>**Год**</u><br/><u>**f_3**</u><br/><u>**qwe**</u>rt
+**`\W`** | Любая не-буква, не-цифра и не подчёркивание | `сом\W` | <u>сом<b>!</b></u>, <u>сом<b>?</b></u>
+**`[..]`** | Один из символов в скобках,<br/>а также любой символ из диапазона a-b | [0-9][0-9A-Fa-f] | <u>**12**</u><br/><u>**1F**</u><br/><u>**4B**</u>
+**`[^..]`** | Любой символ, **кроме** перечисленных | \<[^\>]\> | <u><b>\<1\></b></u><br/><u><b>\<a\></b></u><br/>\<\>\>
+[а-яА-ЯёЁ] | Буква “ё” не включается в общий диапазон букв (`\w`)! Поэтому для указания полного диапазона кириллицы приходится писать расширенный диапазон  
+**`[abc-]`, `[-1]`** | если нужен знак минус, его нужно указать последним или первым (иначе он будет интерпретироваться как диапазон)
+**`[(+\\\]]`** | внутри квадратных скобок нужно экранировать только закрывающую квадратную скобку "**]**" и знак "**`\`**"
+**`\b`** | Начало или конец слова (слева пусто или не-буква, справа буква и наоборот).<br/>В отличие от предыдущих соответствует позиции, а не символу | `\bвал` | <u>**вал**</u><br/>перевал
+**`\B`** | Не граница слова: либо и слева, и справа буквы, либо и слева, и справа НЕ буквы | `\Bвал` | пере<u>**вал**</u><br/>вал
+
+### Квантификаторы (указание количества повторений)
+
+Шаблон | Описание | Пример | Применяем к тексту
+:-----:|----------|--------|-------------------
+**`{n}`**| Ровно "n" повторений | `\d{4}` | 1<br/>12<br/>123<br/><u>**1234**</u><br/><u>**1234**</u>5
+**`{m,n}`** | От "m" до "n" повторений включительно | `\d{2,4}` | 1<br/><u>**12**</u><br/><u>**123**</u><br/><u>**1234**</u><br/><u>**1234**</u>5
+**`{m,}`** | Не менее "m" повторений | `\d{3,}` | 1<br/>12<br/><u>**123**</u><br/><u>**1234**</u><br/>...
+**`{,n}`** | Не более "n" повторений | `\d{,2}` | <u>**1**</u><br/><u>**12**</u><br/><u>**12**</u>3
+**`?`** | Ноль или одно вхождение, синоним `{0,1}` | `вал?` | <u>**вал**</u><br/><u>**валы**</u><br/><u>**вал**</u>ов
+**`*`** | Ноль или более вхождений, синоним `{0,}` | `СУ\d*` | <u>**СУ**</u><br/><u>**СУ1**</u><br/><u>**СУ12**</u>...
+**`+`** | Одно или более, синоним `{1,}` | `a\)+` | <u>**a)**</u><br/><u>**a))**</u><br/><u>**a)))**</u><br/>b<u>**a)**</u>])
+__*?<br/>+?<br/>??<br/>{m,n}?<br/>{,n}?<br/>{m,}?__ |По умолчанию квантификаторы жадные - захватывают максимально возможное число символов. Добавление символа "?" делает их ленивыми, они захватывают минимально возможное число символов | `\(.*\)` | <u>**(a+b)\*(c+d)\*(e+f)**</u>
+&nbsp;|&nbsp;| `\(.*?\)` | <u>**(a+b)**</u>\*(c+d)\*(e+f)
+
+### Больше метасимволов
+
+Есть некоторые метасимволы, которые мы еще не изучили. Большинство из них будут рассмотрены в этом разделе.
+
+* "**`|`**"
+
+    Соответствует оператору ИЛИ. Если А и В являются регулярными выражениями, то `A|B` будет соответствовать любая строка, которая соответствует А или В. Метасимвол "**|**" имеет очень низкий приоритет для того, чтобы заставить его работать разумно, когда вы чередуете несколько символов строки. `Crow|Servo` будет искать соответствие либо **Crow**, либо **Servo**, а не `Cro('w' или 'S')ervo`.
+
+* "**`^`**"
+
+    Ищет соответствие только в начале строки. Если включен флаг `MULTILINE`, как говорилось выше, то происходит сравнение и для каждой части после символа новой строки.
+
+    Например, если вы хотите найти только те строки, у которых в начале имеется From, то в регулярном выражении записывается `^From`.
+
+* "**`$`**"
+
+    То же, что "**^**", но в конце строки, которая определяется либо, собственно по концу строки как таковому, либо по символу новой строки.
+
+* **`\A`**
+
+    Совпадение только в начале строки, то есть тоже, что "**^**", но не зависит от флага MULTILINE
+
+* **`\Z`**
+
+    Совпадение только в конце строки, то есть тоже, что "**$**", но не зависит от флага MULTILINE
+
+* **`\b`**
+
+    Граница слова. Слово определяется как последовательность символов чисел и/или букв, так что границы слова представляют пробелы или любые символы, не относящиеся к перечисленным.    
+
+### Группировка
+
+Часто необходимо получить больше информации, чем просто узнать, находит ли регулярное выражение соответствие или нет. Регулярные выражения часто используются для разрезания строк написанием регулярных выражений, разделенных на несколько подгрупп, которые соответствуют различным компонентам запроса. Например, в стандарте RFC-822 в заголовке имеются различные поля, разделенные двоеточием:
+
+```
+From: author@example.com
+User-Agent: Thunderbird 1.5.0.9 (X11/20061227)
+MIME-Version: 1.0
+To: editor@example.com
+```
+
+Это может быть обработано написанием регулярного выражения, которое соответствует всей строке заголовка, и в нем есть одна группа, которая соответствует имени заголовка, и другая группа, которая соответствует значению заголовка.
+
+Группы обозначаются метасимволами в виде круглых скобок '**`(`**', '**`)`**'. '**`(`**' и '**`)`**' имеют такой же смысл, как в математических выражениях; они группируют вместе выражения, содержащиеся в них, и вы можете повторять содержание группы повторяющими квалификаторами, такими как `*`, `+`, `?` и `{m, n}`. Например, `(ab)*` будет соответствовать нулю или более повторений `ab`.
+
+Группы, определяемые скобками, также захватывают начальные и конечные индексы совпадающего текста. Группы нумеруются, начина с 0. Группа 0 имеется всегда, это само регулярное выражение целиком.
+
+Подгруппы нумеруются слева направо, от 1 и далее. Группы могут быть вложенными; для того чтобы определить число вложений, просто подсчитываем слева направо символы открывающей скобки.
+
+### Обратные ссылки
+
+Обратные ссылки в шаблоне позволяют вам указать, что содержание ранее захваченной группы также должно быть найдено в текущей позиции строки. Например, `\1` соответствует тому, что содержание группы `1` в точности повторяется в текущей позиции.
+
+Например, следующая "регулярка" обнаруживает в строке дважды подряд повторяющиеся слова:
+
+```regexp
+(\b\w+)\s+\1
+```
+### Ленивая квантификация
+
+Предположим, перед нами стоит задача — найти все HTML-теги в строке
+
+```html
+<p><b>Tproger</b> — мой <i>любимый</i> сайт о программировании!</p>
+```
+
+Очевидное решение `<.*>` здесь не сработает — оно найдёт всю строку целиком, т.к. она начинается с тега абзаца (`<p>`) и им же заканчивается. То есть содержимым тега будет считаться строка:
+
+```html
+p><b>Tproger</b> — мой <i>любимый</i> сайт о программировании!</p
+```
+
+Это происходит из-за того, что по умолчанию квантификатор работают по т.н. "жадному" алгоритму — старается вернуть как можно более длинную строку, соответствующую условию. Решить проблему можно двумя способами. Первый — использовать выражение `<[^>]*>`, которое запретит считать содержимым тега правую угловую скобку. Второй — объявить квантификатор не "жадным", а "ленивым". Делается это с помощью добавления справа к квантификатору символа "?". Т.е. для поиска всех тегов выражение обратится в `<.*?>`.
+
+## Регулярки в C#
+
+Основная функциональность регулярных выражений в .NET сосредоточена в пространстве имен *System.Text.RegularExpressions*. А центральным классом при работе с регулярными выражениями является класс **Regex**. Например, у нас есть некоторый текст и нам надо найти в нем все словоформы какого-нибудь слова. С классом **Regex** это сделать очень просто:
+
+```cs
+string s = "Бык тупогуб, тупогубенький бычок, у быка губа бела была тупа";
+Regex regex = new Regex(@"туп(\w*)");
+MatchCollection matches = regex.Matches(s);
+if (matches.Count > 0)
+{
+    foreach (Match match in matches)
+        Console.WriteLine(match.Value);
+}
+else
+{
+    Console.WriteLine("Совпадений не найдено");
+}
+```
+
+Здесь мы находим в искомой строке все словоформы слова "туп". В конструктор объекта **Regex** передается регулярное выражение для поиска. Выражение `туп(\w*)` обозначает, найти все слова, которые имеют корень "туп" и после которого может стоять различное количество символов. Выражение `\w` означает алфавитно-цифровой символ, а звездочка после выражения указывает на неопределенное их количество - их может быть один, два, три или вообще не быть.
+
+Метод *Matches* класса **Regex** принимает строку, к которой надо применить регулярные выражения, и возвращает коллекцию найденных совпадений.
+
+Каждый элемент такой коллекции представляет объект **Match**. Его свойство *Value* возвращает найденное совпадение.
+
+### Поиск с группами
+
+Метод *Matches* ищет **все** вхождения словоформы *туп*, но на практике чаще приходится искать **одно** совпадение с шаблоном, но разбитое на группы. Для этого больше подходит метод *Match*, который возвращает объект типа **Match**. У этого объекта есть свойство *Success*, по которому мы можем определить найдена ли строка по шаблону и массив *Groups*, в котором находятся значения подгрупп (в группе 0, напоминаю, находится вся найденная строка).
+
+```cs
+string text = "One car red car blue car";
+string pat = @"(\w+)\s+(car)";
+
+Regex r = new Regex(pat, RegexOptions.IgnoreCase);
+
+Match m = r.Match(text);
+int matchCount = 0;
+// можно искать не одно вхождение, а несколько
+while (m.Success)
+{
+    Console.WriteLine("Match"+ (++matchCount));
+    // тут можно было бы перебирать по длине массива Groups, 
+    // но мы по своему шаблону и так знаем, что у нас две подгруппы
+    for (int i = 1; i <= 2; i++)
+    {
+    Console.WriteLine($"Group {i}='{m.Groups[i]}'");
+    }
+    // поиск следующей подстроки соответсвующей шаблону
+    m = m.NextMatch();
+}
+```
+
+Выведет
+
+```
+Match1
+Group 1=One
+Group 2=car
+Match2
+Group 1=red
+Group 2=car
+Match3
+Group 1=blue
+Group 2=car
+```
+
+### Параметр RegexOptions
+
+Класс **Regex** имеет ряд конструкторов, позволяющих выполнить начальную инициализацию объекта. Две версии конструкторов в качестве одного из параметров принимают перечисление *RegexOptions*. Некоторые из значений, принимаемых данным перечислением:
+
+* **Compiled**: при установке этого значения регулярное выражение компилируется в сборку, что обеспечивает более быстрое выполнение
+
+* **CultureInvariant**: при установке этого значения будут игнорироваться региональные различия
+
+* **IgnoreCase**: при установке этого значения будет игнорироваться регистр
+
+* **IgnorePatternWhitespace**: удаляет из строки пробелы и разрешает комментарии, начинающиеся со знака #
+
+* **Multiline**: указывает, что текст надо рассматривать в многострочном режиме. При таком режиме символы "^" и "$" совпадают, соответственно, с началом и концом любой строки, а не с началом и концом всего текста
+
+* **RightToLeft**: приписывает читать строку справа налево
+
+* **Singleline**: устанавливает однострочный режим, а весь текст рассматривается как одна строка
+
+Например:
+
+```cs
+Regex regex = new Regex(
+    @"туп(\w*)", 
+    RegexOptions.IgnoreCase);
+```
+
+При необходимости можно установить несколько параметров:
+
+```cs
+Regex regex = new Regex(
+    @"туп(\w*)", 
+    RegexOptions.Compiled | RegexOptions.IgnoreCase);
+```
+
+Теперь посмотрим на некоторые примеры использования. Возьмем первый пример с скороговоркой "Бык тупогуб, тупогубенький бычок, у быка губа бела была тупа" и найдем в ней все слова, где встречается корень "губ":
+
+```cs
+string s = "Бык тупогуб, тупогубенький бычок, у быка губа бела была тупа";
+Regex regex = new Regex(@"\w*губ\w*");
+```
+
+Так как выражение `\w*` соответствует любой последовательности алфавитно-цифровых символов любой длины, то данное выражение найдет все слова, содержащие корень "губ".
+
+Второй простенький пример - нахождение телефонного номера в формате `111-111-1111`:
+
+```cs
+string s = "456-435-2318";
+Regex regex = new Regex(
+    @"\d{3}-\d{3}-\d{4}");
+```
+
+Если мы точно знаем, сколько определенных символов должно быть, то мы можем явным образом указать их количество в фигурных скобках: `\d{3}` - то есть в данном случае три цифры.
+
+Мы можем не только задать поиск по определенным типам символов - пробелы, цифры, но и задать конкретные символы, которые должны входить в регулярное выражение. Например, перепишем пример с номером телефона и явно укажем, какие символы там должны быть:
+
+```cs
+string s = "456-435-2318";
+Regex regex = new Regex(
+    "[0-9]{3}-[0-9]{3}-[0-9]{4}");
+```
+
+В квадратных скобках задается диапазон символов, которые должны в данном месте встречаться. В итоге данный и предыдущий шаблоны телефонного номера будут эквивалентны.
+
+Также можно задать диапазон для алфавитных символов: `Regex regex = new Regex("[a-v]{5}");` - данное выражение будет соответствовать любому сочетанию пяти символов, в котором все символы находятся в диапазоне от `a` до `v`.
+
+Можно также указать отдельные значения: `Regex regex = new Regex(@"[2]*-[0-9]{3}-\d{4}");`. Это выражение будет соответствовать, например, такому номеру телефона "222-222-2222" (так как первые числа двойки)
+
+С помощью операции `|` можно задать альтернативные символы: `Regex regex = new Regex(@"[2|3]{3}-[0-9]{3}-\d{4}");`. То есть первые три цифры могут содержать только двойки или тройки. Такой шаблон будет соответствовать, например, строкам "222-222-2222" и "323-435-2318". А вот строка "235-435-2318" уже не подпадает под шаблон, так как одной из трех первых цифр является цифра 5.
+
+Итак, у нас такие символы, как `*`, `+` и ряд других используются в качестве специальных символов. И возникает вопрос, а что делать, если нам надо найти, строки, где содержится точка, звездочка или какой-то другой специальный символ? В этом случае нам надо просто экранировать эти символы слешем:
+
+```cs
+Regex regex = new Regex(
+    @"[2|3]{3}\.[0-9]{3}\.\d{4}");
+// этому выражению будет соответствовать строка "222.222.2222"
+```
+
+### Проверка на соответствие строки формату
+
+Нередко возникает задача проверить корректность данных, введенных пользователем. Это может быть проверка электронного адреса, номера телефона, Класс **Regex** предоставляет статический метод *IsMatch*, который позволяет проверить входную строку с шаблоном на соответствие:
+
+```cs
+string pattern = @"^(?("")(""[^""]+?""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +
+                @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9]{2,17}))$";
+while (true)
+{
+    Console.WriteLine("Введите адрес электронной почты");
+    string email = Console.ReadLine();
+ 
+    if (Regex.IsMatch(email, pattern, RegexOptions.IgnoreCase))
+    {
+        Console.WriteLine("Email подтвержден");
+        break;
+    }
+    else
+    {
+        Console.WriteLine("Некорректный email");
+    }
+}
+```
+
+Переменная *pattern* задает регулярное выражение для проверки адреса электронной почты. Данное выражение предлагает нам Microsoft на страницах msdn.
+
+Для проверки соответствия строки шаблону используется метод IsMatch: `Regex.IsMatch(email, pattern, RegexOptions.IgnoreCase)`. Последний параметр указывает, что регистр можно игнорировать. И если введенная строка соответствует шаблону, то метод возвращает **true**.
+
+### Замена и метод Replace
+
+Класс **Regex** имеет метод *Replace*, который позволяет заменить строку, соответствующую регулярному выражению, другой строкой:
+
+```cs
+string s = "Мама  мыла  раму. ";
+string pattern = @"\s+";
+string target = " ";
+Regex regex = new Regex(pattern);
+string result = regex.Replace(s, target);
+```
+
+Данная версия метода *Replace* принимает два параметра: строку с текстом, где надо выполнить замену, и сама строка замены. Так как в качестве шаблона выбрано выражение "\s+ (то есть наличие одного и более пробелов), метод Replace проходит по всему тексту и заменяет несколько подряд идущих пробелов ординарными.
+
+---
+
+## Задание на дом:
+
+1. Реализовать примеры из лекции.
+
+1. Лабораторную по теме "строки" реализовать через регулярки (где это возможно)
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Типы файлов: CSV, XML, JSON.](./t5_file_types.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [ООП. Базовые понятия.](./t6_oop1.md)

+ 808 - 0
articles/t5_thread_async.md

@@ -0,0 +1,808 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Исключения. Null.](./t5_exception.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [Работа с файловой системой и файлами.](./t5_files.md)
+
+# Многопоточность
+
+## Введение в многопоточность. Класс **Thread**
+
+Одним из ключевых аспектов в современном программировании является **многопоточность**. Ключевым понятием при работе с многопоточностью является **поток**. **Поток** предствляет некоторую часть кода программы. При выполнении программы каждому потоку выделяется определенный квант времени. И при помощи многопоточности мы можем выделить в приложении несколько потоков, которые будут выполнять различные задачи одновременно. Если у нас, допустим, графическое приложение, которое посылает запрос к какому-нибудь серверу или считывает и обрабатывает огромный файл, то без многопоточности у нас бы блокировался графический интерфейс на время выполнения задачи. А благодаря потокам мы можем выделить отправку запроса или любую другую задачу, которая может долго обрабатываться, в отдельный поток. Поэтому, к примеру, клиент-серверные приложения (и не только они) практически не мыслимы без многопоточности.
+
+Основной функционал для использования потоков в приложении сосредоточен в пространстве имен `System.Threading`. В нем определен класс, представляющий отдельный поток - класс **Thread**.
+
+## Параллельное программирование и библиотека TPL
+
+В эпоху многоядерных машин, которые позволяют параллельно выполнять сразу несколько процессов, стандартных средств работы с потоками в .NET уже оказалось недостаточно. Поэтому во фреймворк .NET была добавлена библиотека параллельных задач TPL (Task Parallel Library), основной функционал которой располагается в пространстве имен `System.Threading.Tasks`. Данная библиотека позволяет распараллелить задачи и выполнять их сразу на нескольких процессорах, если на целевом компьютере имеется несколько ядер. Кроме того, упрощается сама работа по созданию новых потоков. Поэтому начиная с .NET 4.0. рекомендуется использовать именно TPL и ее классы для создания многопоточных приложений, хотя стандартные средства и класс **Thread** по-прежнему находят широкое применение.
+
+### Задачи и класс Task
+
+В основе библиотеки TPL лежит концепция задач, каждая из которых описывает отдельную продолжительную операцию. В библиотеке классов .NET задача представлена специальным классом - классом **Task**, который находится в пространстве имен `System.Threading.Tasks`. Данный класс описывает отдельную задачу, которая запускается асинхронно в одном из потоков из пула потоков. Хотя ее также можно запускать синхронно в текущем потоке.
+
+Для определения и запуска задачи можно использовать различные способы. Первый способ создание объекта **Task** и вызов у него метода *Start*:
+
+```cs
+Task task = new Task(
+    () => Console.WriteLine("Hello Task!")
+);
+task.Start();
+```
+
+>Если запустить код в таком виде, то строку "Hello Task" мы в консоли не увидим. Дело в том, что процесс запуска потока не моментальный и программа успеет завершиться до запуска потока.
+
+```
+/home/kei/RiderProjects/asyncAwait/bin/Debug/net8.0/asyncAwait
+
+Process finished with exit code 0.
+```
+
+В качестве параметра объект **Task** принимает делегат **Action**, то есть мы можем передать любое действие, которое соответствует данному делегату, например, лямбда-выражение, как в данном случае, или ссылку на какой-либо метод. То есть в данном случае при выполнении задачи на консоль будет выводиться строка "Hello Task!".
+
+А метод *Start* собственно запускает задачу.
+
+Второй способ заключается в использовании статического метода `Task.Factory.StartNew()`. Этот метод также в качестве параметра принимает делегат **Action**, который указывает, какое действие будет выполняться. При этом этот метод сразу же запускает задачу:
+
+```cs
+Task task = Task.Factory.StartNew(
+    () => Console.WriteLine("Hello Task!")
+);
+```
+
+В качестве результата метод возвращает запущенную задачу.
+
+Третий способ определения и запуска задач представляет использование статического метода `Task.Run()`:
+
+```cs
+Task task = Task.Run(
+    () => Console.WriteLine("Hello Task!")
+);
+```
+
+Метод `Task.Run()` также в качестве параметра может принимать делегат **Action** - выполняемое действие и возвращает объект **Task**.
+
+Определим небольшую программу, где используем все эти способы:
+
+```cs
+using System;
+using System.Threading.Tasks;
+ 
+Task task1 = new Task(
+    () => Console.WriteLine("Task1 is executed"));
+task1.Start();
+
+Task task2 = Task.Factory.StartNew(
+    () => Console.WriteLine("Task2 is executed"));
+
+Task task3 = Task.Run(
+    () => Console.WriteLine("Task3 is executed"));
+    
+Console.ReadLine();
+```
+
+### Ожидание задачи
+
+Важно понимать, что задачи не выполняются последовательно. Первая запущенная задача может завершить свое выполнение после последней задачи.
+
+Или рассмотрим еще один пример:
+
+```cs
+using System;
+using System.Threading;
+ 
+Task task = new Task(Display);
+task.Start();
+    
+Console.WriteLine(
+    "Завершение метода Main");
+
+Console.ReadLine();
+
+static void Display()
+{
+    Console.WriteLine(
+        "Начало работы метода Display");
+
+    Console.WriteLine(
+        "Завершение работы метода Display");
+}
+```
+
+Класс **Task** в качестве параметра принимает метод *Display*, который соответствует делегату **Action**. Далее чтобы запустить задачу, вызываем метод *Start*: `task.Start()`, и после этого метод *Display* начнет выполняться во вторичном потоке. В конце метода Main выводит некоторый маркер-строку, что метод _Main_ завершился.
+
+Однако в данном случае консольный вывод может выглядеть следующим образом:
+
+```
+Завершение метода Main
+Начало работы метода Display
+Завершение работы метода Display
+```
+
+То есть мы видим, что даже когда основной код в методе _Main_ уже отработал, запущенная ранее задача ещё не завершилась.
+
+Чтобы указать, что метод _Main_ должен подождать до конца выполнения задачи, нам надо использовать метод *Wait*:
+
+```cs
+Task task = new Task(Display);
+task.Start();
+task.Wait();
+Console.WriteLine("Завершение метода Main");
+Console.ReadLine();
+```
+
+### Свойства класса **Task**
+
+Класс **Task** имеет ряд свойств, с помощью которых мы можем получить информацию об объекте. Некоторые из них:
+
+* *AsyncState*: возвращает объект состояния задачи
+* *CurrentId*: возвращает идентификатор текущей задачи
+* *Exception*: возвращает объект исключения, возникшего при выполнении задачи
+* *Status*: возвращает статус задачи
+
+## Aсинхронное программирование. Асинхронные методы, **async** и **await**
+
+Асинхронность позволяет вынести отдельные задачи из основного потока в специальные асинхронные методы или блоки кода. Особенно это актуально в графических программах, где продолжительные задачи могу блокировать интерфейс пользователя. И чтобы этого не произошло, нужно задействовать асинхронность. Также асинхронность несет выгоды в веб-приложениях при обработке запросов от пользователей, при обращении к базам данных или сетевым ресурсам. При больших запросах к базе данных асинхронный метод просто "уснёт" на время, пока не получит данные от БД, а основной поток сможет продолжить свою работу. В синхронном же приложении, если бы код получения данных находился в основном потоке, этот поток просто бы блокировался на время получения данных.
+
+Ключевыми для работы с асинхронными вызовами в C# являются два ключевых слова: **async** и **await**, цель которых - упростить написание асинхронного кода. Они используются вместе для создания асинхронного метода.
+
+Асинхонный метод обладает следующими признаками:
+
+* В заголовке метода используется модификатор **async**
+* Метод содержит одно или несколько выражений **await**
+* В качестве возвращаемого типа используется один из следующих:
+
+    * `void`
+    * `Task`
+    * `Task<T>`
+    * `ValueTask<T>`
+
+Асинхронный метод, как и обычный, может использовать любое количество параметров или не использовать их вообще. Однако асинхронный метод не может определять параметры с модификаторами **out** и **ref**.
+
+Также стоит отметить, что слово **async**, которое указывается в определении метода, не делает автоматически метод асинхронным. Оно лишь указывает, что данный метод **может** содержать одно или несколько выражений **await**.
+
+Рассмотрим пример асинхронного метода:
+
+```cs
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+// вызов асинхронного метода 
+FactorialAsync();   
+
+Console.WriteLine(
+    "Введите число: ");
+
+int n = Int32.Parse(Console.ReadLine());
+
+Console.WriteLine(
+    $"Квадрат числа равен {n * n}");
+    
+Console.Read();
+
+
+static void Factorial()
+{
+    int result = 1;
+    for(int i = 1; i <= 6; i++)
+    {
+        result *= i;
+    }
+    Thread.Sleep(8000);
+    Console.WriteLine(
+        $"Факториал равен {result}");
+}
+
+// определение асинхронного метода
+static async void FactorialAsync()
+{
+    // выполняется синхронно
+    Console.WriteLine(
+        "Начало метода FactorialAsync");
+
+    // выполняется асинхронно     
+    await Task.Run(()=>Factorial());
+
+    Console.WriteLine(
+        "Конец метода FactorialAsync");
+}
+```
+
+Здесь прежде всего определен обычный метод подсчета факториала. Для имитации долгой работы в нем используется задержка на `8` секунд с помощью метода `Thread.Sleep()`. Условно это некоторый метод, который выполняет некоторую работу продолжительное время. Но для упрощения понимания он просто подсчитывает факториал числа `6`.
+
+Также здесь определен асинхронный метод *FactorialAsync()*. Асинхронным он является потому, что имеет в определении перед возвращаемым типом модификатор **async**, его возвращаемым типом является _void_, и в теле метода определено выражение **await**.
+
+Выражение **await** определяет задачу, которая будет выполняться асинхронно. В данном случае подобная задача представляет выполнение функции факториала:
+
+```cs
+await Task.Run(()=>Factorial());
+```
+
+>По негласным правилам в названии асинхроннных методов принято использовать суффикс *Async* - *FactorialAsync()*, хотя в принципе это необязательно делать.
+
+И в методе _Main_ мы вызываем этот асинхронный метод.
+
+Посмотрим, какой у программы будет консольный вывод:
+
+```
+Начало метода FactorialAsync
+Введите число: 
+7
+Квадрат числа равен 49
+Конец метода Main
+Факториал равен 720
+Окончание метода FactorialAsync
+```
+
+Разберем поэтапно, что здесь происходит:
+
+* Запускается метод _Main_, в котором вызывается асинхронный метод _FactorialAsync_.
+* Метод _FactorialAsync_ начинает выполняться синхронно вплоть до выражения **await**.
+* Выражение **await** запускает асинхронную задачу `Task.Run(()=>Factorial())`
+* Пока выполняется асинхронная задача `Task.Run(()=>Factorial())` (а она может выполняться довольно продожительное время), выполнение кода возвращается в вызывающий метод - то есть в метод _Main_. В методе _Main_ нам будет предложено ввести число для вычисления квадрата числа.
+
+    В этом и преимущество асинхронных методов - асинхронная задача, которая может выполняться довольно долгое время, не блокирует метод _Main_, и мы можем продолжать работу с ним, например, вводить и обрабатывать данные.
+
+* Когда асинхронная задача завершила свое выполнение (в случае выше - подсчитала факториал числа), продолжает работу асинхронный метод _FactorialAsync_, который вызвал асинхронную задачу.
+
+Функция факториала, возможно, представляет не самый показательный пример, так как в реальности в данном случае нет смысла делать ее асинхронной. Но рассмотрим другой пример - чтение-запись файла:
+
+```cs
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.IO;
+ 
+ReadWriteAsync();
+    
+Console.WriteLine("Некоторая работа");
+Console.Read();
+
+
+static async void ReadWriteAsync()
+{
+    string s = "Hello world! One step at a time";
+
+    // hello.txt - файл, который будет записываться и считываться
+    using (StreamWriter writer = new StreamWriter("hello.txt", false))
+    {
+        // асинхронная запись в файл
+        await writer.WriteLineAsync(s);  
+    }
+    using (StreamReader reader = new StreamReader("hello.txt"))
+    {
+        // асинхронное чтение из файла
+        string result = await reader.ReadToEndAsync();  
+        Console.WriteLine(result);
+    }
+} 
+```
+
+Асинхронный метод *ReadWriteAsync* выполняет запись в файл некоторой строки и затем считывает записанный файл. Подобные операции могут занимать продолжительное время, особенно при больших объемах данных, поэтому такие операции лучше делать асинхронными.
+
+Фреймворк .NET уже имеет встроенную поддержку таких операций. Например, в классе **StreamWriter** определен метод *WriteLineAsync*. По сути он уже представляет асинхронную операцию и принимает в качестве параметра некоторую строку, которую надо записать в файл. Поскольку этот метод представляет асинхронную операцию, то вызов этого метода мы можем оформить в выражение **await**:
+
+```cs
+// асинхронная запись в файл
+await writer.WriteLineAsync(s);  
+```
+
+Аналогично в классе **StreamReader** определен метод *ReadToEndAsync*, который также представляет асинхронную операцию и который возвращает весь считанный текст.
+
+Во фреймворке определено много подобных методов. Как правило, они связаны с работой с файлами, отправкой сетевых запросов или запросов к базе данных. Их легко узнать по суффиксу *Async*. То есть если метод имеет подобный суффикс в названии, то с большей степенью вероятности его можно использовать в выражении **await**.
+
+Далее в методе _Main_ вызывается асинхронный метод *ReadWriteAsync*:
+
+```cs
+ReadWriteAsync();
+            
+Console.WriteLine("Некоторая работа");
+Console.Read();
+```
+
+И опять же, когда выполнение в методе *ReadWriteAsync* доходит до первого выражения **await**, управление возвращается в метод *Main*, и мы можем продолжать с ним работу. Запись в файл и считывание файла будут производиться параллельно и не будут блокировать работу метода *Main*.
+
+### Определение асинхронной операции
+
+Как выше уже было сказано, фреймворк .NET имеет много встроенных методов, которые представляют асинхронную операцию. Они заканчиваются на суффикс *Async*. И перед вызывами подобных методов мы можем указывать оператор **await**. Например:
+
+```cs
+StreamWriter writer = new StreamWriter("hello.txt", false);
+// асинхронная запись в файл
+await writer.WriteLineAsync("Hello");  
+```
+
+Либо мы сами можем определить асинхронную операцию, используя метод `Task.Run()`:
+
+```cs
+static void Factorial()
+{
+    int result = 1;
+    for (int i = 1; i <= 6; i++)
+    {
+        result *= i;
+    }
+    Thread.Sleep(8000);
+    Console.WriteLine($"Факториал равен {result}");
+}
+// определение асинхронного метода
+static async void FactorialAsync()
+{
+    // вызов асинхронной операции
+    await Task.Run(()=>Factorial()); 
+}
+```
+
+Можно определить асинхронную операцию с помощью лямбда-выражения:
+
+```cs
+static async void FactorialAsync()
+{
+    await Task.Run(() =>
+    {
+        int result = 1;
+        for (int i = 1; i <= 6; i++)
+        {
+            result *= i;
+        }
+        Thread.Sleep(8000);
+        Console.WriteLine($"Факториал равен {result}");
+    });
+}
+```
+
+### Передача параметров в асинхронную операцию
+
+Выше вычислялся факториал `6`, но, допустим, мы хотим вычислять факториалы разных чисел:
+
+```cs
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+ 
+
+FactorialAsync(5);
+FactorialAsync(6);
+Console.WriteLine("Некоторая работа");
+Console.Read();
+
+
+static void Factorial(int n)
+{
+    int result = 1;
+    for (int i = 1; i <= n; i++)
+    {
+        result *= i;
+    }
+    Thread.Sleep(5000);
+    Console.WriteLine($"Факториал равен {result}");
+}
+// определение асинхронного метода
+static async void FactorialAsync(int n)
+{
+    await Task.Run(()=>Factorial(n));
+}
+```
+
+### Получение результата из асинхронной операции
+
+Асинхронная операция может возвращать некоторый результат, получить который мы можем так же, как и при вызове обычного метода:
+
+```cs
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+ 
+
+FactorialAsync(5);
+FactorialAsync(6);
+Console.Read();
+
+
+static int Factorial(int n)
+{
+    int result = 1;
+    for (int i = 1; i <= n; i++)
+    {
+        result *= i;
+    }
+    return result;
+}
+// определение асинхронного метода
+static async void FactorialAsync(int n)
+{
+    int x = await Task.Run(()=>Factorial(n));
+    Console.WriteLine($"Факториал равен {x}");
+}
+```
+
+Метод *Factorial* возвращает значение типа **int**, это значение мы можем получить, просто присвоив результат асинхронной операции переменной данного типа: `int x = await Task.Run(()=>Factorial(n));`
+
+### Возвращение результата из асинхронного метода
+
+В качестве возвращаемого типа в асинхронном методе должны использоваться типы `void`, `Task`, `Task<T>` или `ValueTask<T>`
+
+#### void
+
+При использовании ключевого слова **void** асинхронный метод ничего не возвращает:
+
+```cs
+static void Factorial(int n)
+{
+    int result = 1;
+    for (int i = 1; i <= n; i++)
+    {
+        result *= i;
+    }
+    Console.WriteLine($"Факториал равен {result}");
+}
+// определение асинхронного метода
+static async void FactorialAsync(int n)
+{
+    await Task.Run(()=>Factorial(n));
+}
+```
+
+#### Task
+
+Возвращение объекта типа **Task**:
+
+```cs
+using System;
+using System.Threading.Tasks;
+
+FactorialAsync(5);
+FactorialAsync(6);
+Console.WriteLine("Некоторая работа");
+Console.Read();
+
+static void Factorial(int n)
+{
+    int result = 1;
+    for (int i = 1; i <= n; i++)
+    {
+        result *= i;
+    }
+    Console.WriteLine($"Факториал равен {result}");
+}
+    
+// определение асинхронного метода
+static async Task FactorialAsync(int n)
+{
+    await Task.Run(()=>Factorial(n));
+}
+```
+
+Формально метод *FactorialAsync* не использует оператор **return** для возвращения результата. Однако если в асинхронном методе выполняется в выражении **await** асинхронная операция, то мы можем возвращать из метода объект **Task**.
+
+#### `Task<T>`
+
+Метод может возвращать некоторое значение. Тогда возвращаемое значение оборачивается в объект **Task**, а возвращаемым типом является `Task<T>`:
+
+```cs
+using System;
+using System.Threading.Tasks;
+
+int n1 = await FactorialAsync(5);
+int n2 = await FactorialAsync(6);
+Console.WriteLine($"n1={n1}  n2={n2}");
+Console.Read();
+
+static int Factorial(int n)
+{
+    int result = 1;
+    for (int i = 1; i <= n; i++)
+    {
+        result *= i;
+    }
+    return result;
+}
+// определение асинхронного метода
+static async Task<int> FactorialAsync(int n)
+{
+    return await Task.Run(()=>Factorial(n));
+}
+```
+
+В данном случае функция *Factorial* возвращает значение типа **int**. В асинхронном методе *FactorialAsync* мы получаем и **возвращаем** это число. Поэтому возвращаемым типом в данном случае является типа `Task<int>`. Если бы метод *Factorial* возвращал строку, то есть данные типа **string**, то возвращаемым типом асинхронного метода был бы тип `Task<string>`
+
+Чтобы получить результат асинхронного метода в методе _Main_, который тоже определен как асинхронный, применяем оператор **await** при вызове *FactorialAsync*.
+
+#### `ValueTask<T>`
+
+Использование типа `ValueTask<T>` во многом аналогично применению `Task<T>` за исключением некоторых различий в работе с памятью, поскольку **ValueTask** - структура, а **Task** - класс. По умолчанию тип **ValueTask** недоступен, и чтобы использовать его, вначале надо установить через **NuGet** пакет `System.Threading.Tasks.Extensions`.
+
+### Последовательный и параллельный вызов асинхронных операций
+
+Асинхронный метод может содержать множество выражений **await**. Когда система встречает в блоке кода оператор **await**, то выполнение в асинхронном методе останавливается, пока не завершится асинхронная задача. После завершения задачи управление переходит к следующему оператору **await** и так далее. Это позволяет вызывать асинхронные задачи последовательно в определенном порядке. Например:
+
+```cs
+using System;
+using System.Threading.Tasks;
+
+FactorialAsync();
+Console.Read();
+
+static void Factorial(int n)
+{
+    int result = 1;
+    for (int i = 1; i <= n; i++)
+    {
+        result *= i;
+    }
+    Console.WriteLine($"Факториал числа {n} равен {result}");
+}
+// определение асинхронного метода
+static async void FactorialAsync()
+{
+    await Task.Run(() => Factorial(4));
+    await Task.Run(() => Factorial(3));
+    await Task.Run(() => Factorial(5));
+}
+```
+
+Консольный вывод данной программы:
+
+```
+Факториал числа 4 равен 24
+Факториал числа 3 равен 6
+Факториал числа 5 равен 120
+```
+
+То есть мы видим, что факториалы вычисляются последовательно. И в данном случае вывод строго детерминирован.
+
+Нередко такая последовательность бывает необходима, если одна задача зависит от результатов другой.
+
+Однако не всегда существует подобная зависимость между задачами. В этом случае мы можем запустить все задачи параллельно и через метод *Task.WhenAll* отследить их завершение. Например, изменим метод *FactorialAsync*:
+
+```cs
+static async void FactorialAsync()
+{
+    Task t1 = Task.Run(() => Factorial(4));
+    Task t2 = Task.Run(() => Factorial(3));
+    Task t3 = Task.Run(() => Factorial(5));
+    await Task.WhenAll(new[] { t1, t2, t3 });
+}
+```
+
+Вначале запускаются три задачи. Затем *Task.WhenAll* создает новую задачу, которая будет автоматически выполнена после выполнения всех предоставленных задач, то есть задач `t1`, `t2`, `t3`. А с помощью оператора **await** ожидаем ее завершения.
+
+В итоге все три задачи теперь будут запускаться параллельно, однако вызывающий метод *FactorialAsync* благодаря оператору **await** все равно будет ожидать, пока они все не завершатся. И в этом случае вывод программы не детерминирован. Например, он может быть следующим:
+
+```
+Факториал числа 5 равен 120
+Факториал числа 4 равен 24
+Факториал числа 3 равен 6
+```
+
+И если задача возвращает какое-нибудь значение, то это значение потом можно получить с помощью свойства Result.
+
+### Обработка ошибок в асинхронных методах
+
+Обработка ошибок в асинхронных методах, использующих ключевые слова **async** и **await**, имеет свои особенности.
+
+Для обработки ошибок выражение **await** помещается в блок **try**:
+
+```cs
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+ 
+
+FactorialAsync(-4);
+FactorialAsync(6);
+Console.Read();
+
+static void Factorial(int n)
+{
+    if (n < 1)
+        throw new Exception($"{n} : число не должно быть меньше 1");
+    int result = 1;
+    for (int i = 1; i <= n; i++)
+    {
+        result *= i;
+    }
+    Console.WriteLine($"Факториал числа {n} равен {result}");
+}
+    
+static async void FactorialAsync(int n)
+{
+    try
+    {
+        await Task.Run(() => Factorial(n));
+    }
+    catch (Exception ex)
+    {
+        Console.WriteLine(ex.Message);
+    }
+}
+```
+
+В данном случае метод *Factorial* генерирует исключение, если методу передается число меньше `1`.
+
+Для обработки исключения в методе *FactorialAsync* выражение **await** помещено в блок **try**.
+
+В методе *Main* вызывается асинхронный метод с передачей ему отрицательного числа: `FactorialAsync(-4)`, что привет к генерации исключения. Однако программа не остановит аварийно свою работу, а обработает исключение и продолжит дальнейшие вычисления.
+
+Следует учитывать, что если мы запускаем данный код в режиме отладки в Visual Studio, то VS просигнализирует нам о генерации исключении и остановит выполнение на строке `throw new Exception($"{n} : число не должно быть меньше 1");`. В режиме запуска без отладки VS не будет останавливаться.
+
+#### Исследование исключения
+
+При возникновении ошибки у объекта **Task**, представляющего асинхронную задачу, в которой произошла ошибка, свойство *IsFaulted* имеет значение **true**. Кроме того, свойство *Exception* объекта **Task** содержит всю информацию об ошибке. Чтобы проинспектировать свойство, изменим метод *FactorialAsync* следующим образом:
+
+```cs
+static async void FactorialAsync(int n)
+{
+    Task task = null;
+    try
+    {
+        task = Task.Run(()=>Factorial(n));
+        await task;
+    }
+    catch (Exception ex)
+    {
+        Console.WriteLine(task.Exception.InnerException.Message);
+        Console.WriteLine($"IsFaulted: {task.IsFaulted}");
+    }
+}
+```
+
+И если мы передадим в метод число `-1`, то `task.IsFaulted` будет равно **true**.
+
+#### Обработка нескольких исключений. WhenAll
+
+Если мы ожидаем выполнения сразу нескольких задач, например, с помощью *Task.WhenAll*, то мы можем получить сразу несколько исключений одномоментно для каждой выполняемой задачи. В этом случае мы можем получить все исключения из свойства *Exception.InnerExceptions*:
+
+```cs
+static async Task DoMultipleAsync()
+{
+    Task allTasks = null;
+ 
+    try
+    {
+        Task t1 = Task.Run(()=>Factorial(-3));
+        Task t2 = Task.Run(() => Factorial(-5));
+        Task t3 = Task.Run(() => Factorial(-10));
+ 
+        allTasks = Task.WhenAll(t1, t2, t3);
+        await allTasks;
+    }
+    catch (Exception ex)
+    {
+        Console.WriteLine("Исключение: " + ex.Message);
+        Console.WriteLine("IsFaulted: " + allTasks.IsFaulted);
+        foreach (var inx in allTasks.Exception.InnerExceptions)
+        {
+            Console.WriteLine("Внутреннее исключение: " + inx.Message);
+        }
+    }
+}
+```
+
+Здесь в три вызова метода факториала передаются заведомо некорректные числа: `-3`, `-5`, `-10`. Таким образом, при всех трх вызовах будет сгенерирована ошибка.
+
+Хотя блок **catch** через переменную `Exception ex` будет получать одно перехваченное исключение, но с помощью коллекции *Exception.InnerExceptions* мы сможем получить инфрмацию обо всех возникших исключениях.
+
+В итоге при выполнении этого метода мы получим следующий консольный вывод:
+
+```
+Исключение: -3 : число не должно быть меньше 1
+IsFaulted: True
+Внутреннее исключение: -3: число не должно быть меньше 1
+Внутреннее исключение: -5: число не должно быть меньше 1
+Внутреннее исключение: -10: число не должно быть меньше 1
+```
+
+#### await в блоках catch и finally
+
+Начиная с версии C# 6.0 в язык была добавлена возможность вызова асинхронного кода в блоках **catch** и **finally**. Так, возьмем предыдущий пример с подсчетом факториала:
+
+```cs
+static async void FactorialAsync(int n)
+{
+    try
+    {
+        await Task.Run(() => Factorial(n)); ;
+    }
+    catch (Exception ex)
+    {
+        await Task.Run(()=>Console.WriteLine(ex.Message));
+    }
+    finally
+    {
+        await Task.Run(() => Console.WriteLine("await в блоке finally"));
+    }
+}
+```
+
+### Отмена асинхронных операций
+
+Для отмены асинхронных операций используются классы **CancellationToken** и **CancellationTokenSource**.
+
+**CancellationToken** содержит информацию о том, надо ли отменять асинхронную задачу. Асинхронная задача, в которую передается объект **CancellationToken**, периодически проверяет состояние этого объекта. Если его свойство *IsCancellationRequested* равно **true**, то задача должна остановить все свои операции.
+
+Для создания объекта **CancellationToken** применяется объект **CancellationTokenSource**. Кроме того, при вызове у **CancellationTokenSource** метода *Cancel()* у объекта **CancellationToken** свойство *IsCancellationRequested* будет установлено в *true*.
+
+Рассмотрим применение этих классов на примере:
+
+```cs
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+ 
+CancellationTokenSource cts = new CancellationTokenSource();
+CancellationToken token = cts.Token;
+FactorialAsync(6, token);
+Thread.Sleep(3000);
+cts.Cancel();
+Console.Read();
+
+static void Factorial(int n, CancellationToken token)
+{
+    int result = 1;
+    for (int i = 1; i <= n; i++)
+    {
+        if (token.IsCancellationRequested)
+        {
+            Console.WriteLine(
+                "Операция прервана токеном");
+            return;
+        }
+        result *= i;
+        Console.WriteLine(
+            $"Факториал числа {i} равен {result}");
+        Thread.Sleep(1000);
+    }
+}
+// определение асинхронного метода
+static async void FactorialAsync(int n, CancellationToken token)
+{
+    if(token.IsCancellationRequested)
+        return;
+    await Task.Run(()=>Factorial(n, token));
+}
+```
+
+Для создания токена определяется объект **CancellationTokenSource**. Метод *FactorialAsync* в качестве параметра принимает токен, и если где-то во внешнем коде произойдет отмена операции через вызов *cts.Cancel*, то в методе *Factorial* свойство *token.IsCancellationRequested* будет равно **true**, и соответственно при очередной итерации цикла в методе **Factorial** произойдет выход из метода. И асинхронная операция завершится. И мы получим следующий консольный вывод:
+
+```
+Факториал числа 1 равен 1
+Факториал числа 2 равен 2
+Факториал числа 3 равен 6
+Операция была отменена.
+```
+
+---
+
+## Задание на дом:
+
+Реализовать примеры из лекции. Привести текст примера и текст результата, например:
+
+># Конспект лекции "Многопоточность"
+>
+>## Варианты создания и запуска задач
+>
+>```cs
+>Task task1 = new Task(
+>    () => Console.WriteLine("Task1 is executed"));
+>task1.Start();
+>
+>Task task2 = Task.Factory.StartNew(
+>    () => Console.WriteLine("Task2 is executed"));
+>
+>Task task3 = Task.Run(
+>    () => Console.WriteLine("Task3 is executed"));
+>    
+>Console.ReadLine();
+>```
+>
+>```
+>/home/kei/RiderProjects/asyncAwait/bin/Debug/net8.0/asyncAwait
+>Task2 is executed
+>Task1 is executed
+>Task3 is executed
+>
+>Process finished with exit code 0.
+>```
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Исключения. Null.](./t5_exception.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [Работа с файловой системой и файлами.](./t5_files.md)

+ 454 - 0
articles/t6_linq.md

@@ -0,0 +1,454 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Ещё раз про классы. Интерфейсы.](./t6_oop_habr.md) | [Содержание](../readme.md#тема-6-основные-принципы-объектно-ориентированного-программирования) | [Шаблоны проектирования.](./t6_templates.md)
+
+# LINQ
+
+## Основы LINQ
+
+**LINQ** (Language-Integrated Query) представляет простой и удобный язык запросов к источнику данных. В качестве источника данных может выступать объект, реализующий интерфейс **IEnumerable** (например, стандартные коллекции, массивы), набор данных **DataSet**, **документ XML**. Но вне зависимости от типа источника **LINQ** позволяет применить ко всем один и тот же подход для выборки данных.
+
+Существует несколько разновидностей **LINQ**:
+
+* **LINQ to Objects**: применяется для работы с массивами и коллекциями
+
+* **LINQ to Entities**: используется при обращении к базам данных через технологию Entity Framework
+
+* **LINQ to Sql**: технология доступа к данным в MS SQL Server
+
+* **LINQ to XML**: применяется при работе с файлами XML
+
+* **LINQ to DataSet**: применяется при работе с объектом DataSet
+
+* **Parallel LINQ (PLINQ)**: используется для выполнения параллельных запросов
+
+В этой главе речь пойдет прежде всего о **LINQ to Objects**.
+
+В чем же удобство **LINQ**? Посмотрим на простейшем примере. Выберем из массива строки, начинающиеся на определенную букву и отсортируем полученный список:
+
+```cs
+// исходный массив
+string[] teams = {"Бавария", "Боруссия", "Реал Мадрид", "Манчестер Сити", "ПСЖ", "Барселона"};
+ 
+// список для результатов
+var selectedTeams = new List<string>();
+
+foreach(string s in teams)
+{
+    // ищем слова начинающиеся на букву "Б"
+    if (s.ToUpper().StartsWith("Б"))
+        selectedTeams.Add(s);
+}
+
+// сортируем получившийся список
+selectedTeams.Sort();
+ 
+foreach (string s in selectedTeams)
+    Console.WriteLine(s);
+```
+
+Теперь проведем те же действия с помощью **LINQ**:
+
+```cs
+string[] teams = {"Бавария", "Боруссия", "Реал Мадрид", "Манчестер Сити", "ПСЖ", "Барселона"};
+ 
+var selectedTeams = from t in teams // определяем каждый объект из teams как t
+                    where t.ToUpper().StartsWith("Б") //фильтрация по критерию
+                    orderby t  // упорядочиваем по возрастанию
+                    select t; // выбираем объект
+ 
+foreach (string s in selectedTeams)
+    Console.WriteLine(s);
+```
+
+Чтобы использовать функциональность **LINQ**, убедимся, что в файле подключено пространство имен *System.LINQ*.
+
+Итак, код стал меньше и проще. В принципе все выражение можно было бы записать в одну строку: `var selectedTeams = from t in teams where t.ToUpper().StartsWith("Б") orderby t select t`. Но для более понятной логической разбивки я поместил каждое отдельное подвыражение на отдельной строке.
+
+Простейшее определение запроса **LINQ** выглядит следующим образом:
+
+```
+from переменная in набор_объектов
+select переменная;
+```
+
+Итак, что делает этот запрос **LINQ**? Выражение `from t in teams` проходит по всем элементам массива *teams* и определяет каждый элемент как *t*. Используя переменную *t* мы можем проводить над ней разные операции.
+
+Несмотря на то, что мы не указываем тип переменной *t*, выражения **LINQ** являются строго типизированными. То есть среда автоматически распознает, что набор *teams* состоит из объектов **string**, поэтому переменная *t* будет рассматриваться в качестве строки.
+
+Далее с помощью оператора `where` проводится фильтрация объектов, и если объект соответствует критерию (в данном случае начальная буква должна быть "Б"), то этот объект передается дальше.
+
+Оператор `orderby` упорядочивает по возрастанию, то есть сортирует выбранные объекты.
+
+Оператор `select` передает выбранные значения в результирующую выборку, которая возвращается **LINQ**-выражением.
+
+В данном случае результатом выражения **LINQ** является объект `IEnumerable<T>`. Нередко результирующая выборка определяется с помощью ключевого слова **var**, тогда компилятор на этапе компиляции сам выводит тип.
+
+Преимуществом подобных запросов также является и то, что они интуитивно похожи на запросы языка SQL, хотя и имеют некоторые отличия.
+
+### Методы расширения LINQ
+
+Кроме стандартного синтаксиса `from .. in .. select` для создания запроса **LINQ** мы можем применять специальные методы расширения, которые определены для интерфейса **IEnumerable**. Как правило, эти методы реализуют ту же функциональность, что и операторы **LINQ** типа *where* или *orderby*.
+
+Например:
+
+```cs
+string[] teams = { "Бавария", "Боруссия", "Реал Мадрид", "Манчестер Сити", "ПСЖ", "Барселона" };
+ 
+var selectedTeams = teams
+    .Where(t=>t.ToUpper().StartsWith("Б"))
+    .OrderBy(t => t);
+ 
+foreach (string s in selectedTeams)
+    Console.WriteLine(s);
+```
+
+Запрос `teams.Where(t=>t.ToUpper().StartsWith("Б")).OrderBy(t => t)` будет аналогичен предыдущему. Он состоит из цепочки методов *Where* и *OrderBy*. В качестве аргумента эти методы принимают делегат или лямбда-выражение.
+
+Не каждый метод расширения имеет аналог среди операторов **LINQ**, но в этом случае можно сочетать оба подхода. Например, используем стандартный синтаксис **LINQ** и метод расширения *Count()*, возвращающий количество элементов в выборке:
+
+```cs
+int number = (from t in teams where t.ToUpper().StartsWith("Б") select t).Count();
+```
+
+### Список используемых методов расширения LINQ
+
+* **Select**: определяет проекцию выбранных значений
+* **Where**: определяет фильтр выборки
+* **OrderBy**: упорядочивает элементы по возрастанию
+* **OrderByDescending**: упорядочивает элементы по убыванию
+* **ThenBy**: задает дополнительные критерии для упорядочивания элементов возрастанию
+* **ThenByDescending**: задает дополнительные критерии для упорядочивания элементов по убыванию
+* **Join**: соединяет две коллекции по определенному признаку
+* **GroupBy**: группирует элементы по ключу
+* **ToLookup**: группирует элементы по ключу, при этом все элементы добавляются в словарь
+* **GroupJoin**: выполняет одновременно соединение коллекций и группировку элементов по ключу
+* **Reverse**: располагает элементы в обратном порядке
+* **All**: определяет, все ли элементы коллекции удовлятворяют определенному условию
+* **Any**: определяет, удовлетворяет хотя бы один элемент коллекции определенному условию
+* **Contains**: определяет, содержит ли коллекция определенный элемент
+* **Distinct**: удаляет дублирующиеся элементы из коллекции
+* **Except**: возвращает разность двух коллекцию, то есть те элементы, которые создаются только в одной коллекции
+* **Union**: объединяет две однородные коллекции
+* **Intersect**: возвращает пересечение двух коллекций, то есть те элементы, которые встречаются в обоих коллекциях
+* **Count**: подсчитывает количество элементов коллекции, которые удовлетворяют определенному условию
+* **Sum**: подсчитывает сумму числовых значений в коллекции
+* **Average**: подсчитывает cреднее значение числовых значений в коллекции
+* **Min**: находит минимальное значение
+* **Max**: находит максимальное значение
+* **Take**: выбирает определенное количество элементов
+* **Skip**: пропускает определенное количество элементов
+* **TakeWhile**: возвращает цепочку элементов последовательности, до тех пор, пока условие истинно
+* **SkipWhile**: пропускает элементы в последовательности, пока они удовлетворяют заданному условию, и затем возвращает оставшиеся элементы
+* **Concat**: объединяет две коллекции
+* **Zip**: объединяет две коллекции в соответствии с определенным условием
+* **First**: выбирает первый элемент коллекции
+* **FirstOrDefault**: выбирает первый элемент коллекции или возвращает значение по умолчанию
+* **Single**: выбирает единственный элемент коллекции, если коллекция содердит больше или меньше одного элемента, то генерируется исключение
+* **SingleOrDefault**: выбирает первый элемент коллекции или возвращает значение по умолчанию
+* **ElementAt**: выбирает элемент последовательности по определенному индексу
+* **ElementAtOrDefault**: выбирает элемент коллекции по определенному индексу или возвращает значение по умолчанию, если индекс вне допустимого диапазона
+* **Last**: выбирает последний элемент коллекции
+* **LastOrDefault**: выбирает последний элемент коллекции или возвращает значение по умолчанию
+
+## Фильтрация выборки и проекция
+
+### Фильтрация
+
+Для выбора элементов из некоторого набора по условию используется метод *Where*. Например, выберем все _четные_ элементы, которые больше `10`.
+
+Фильтрация с помощью операторов **LINQ**:
+
+```cs
+int[] numbers = { 1, 2, 3, 4, 10, 34, 55, 66, 77, 88 };
+var evens = numbers.Where(i => i % 2 == 0 && i > 10);
+```
+
+Если выражение в методе **Where** для определенного элемента будет равно **true** (в данном случае выражение `i % 2 == 0 && i > 10`), то данный элемент попадает в результирующую выборку.
+
+### Выборка сложных объектов
+
+Допустим, у нас есть класс пользователя:
+
+```cs
+class User
+{
+    public string Name { get;set; }
+    public int Age { get; set; }
+    public List<string> Languages { get; set; }
+    public User()
+    {
+        Languages = new List<string>();
+    }
+}
+```
+
+Создадим набор пользователей и выберем из них тех, которым больше `25` лет:
+
+```cs
+List<User> users = new List<User>
+{
+    new User {
+        Name="Том", 
+        Age=23, 
+        Languages = new List<string> {
+            "английский", "немецкий" }},
+    new User {
+        Name="Боб", 
+        Age=27, 
+        Languages = new List<string> {
+            "английский", "французский" }},
+    new User {
+        Name="Джон", 
+        Age=29, 
+        Languages = new List<string> {
+            "английский", "испанский" }},
+    new User {
+        Name="Элис", 
+        Age=24, 
+        Languages = new List<string> {
+            "испанский", "немецкий" }}
+};
+ 
+var selectedUsers = users.Where(u => u.Age > 25);
+
+foreach (User user in selectedUsers)
+    Console.WriteLine($"{user.Name} - {user.Age}");
+```
+
+Консольный вывод:
+
+```
+Боб - 27
+Джон - 29
+```
+
+### Сложные фильтры
+
+Теперь рассмотрим более сложные фильтры. Например, в классе пользователя есть список языков, которыми владеет пользователь. Что если нам надо отфильтровать пользователей по языку:
+
+Для создания запроса применяется метод *SelectMany*:
+
+```cs
+var selectedUsers = users
+    .SelectMany(u => u.Languages,
+                (u, l) => new { User = u, Lang = l })
+    .Where(u => u.Lang == "английский" && u.User.Age < 28)
+    .Select(u=>u.User);
+```                          
+Результат:
+
+```
+Том - 23
+Боб - 27
+```
+
+Метод *SelectMany()* в качестве первого параметра принимает последовательность, которую надо проецировать, а в качестве второго параметра - функцию преобразования, которая применяется к каждому элементу. На выходе она возвращает `8` пар "пользователь - язык" `new { User = u, Lang = l }`, к которым потом применяетс фильтр с помощью *Where*.
+
+>8 пар в конкретно этом примере, а вообще метод для каждого пользователя возвращает список объектов равный количеству языков пользователя.
+
+### Проекция
+
+Проекция позволяет спроектировать из текущего типа выборки какой-то другой тип. Для проекции используется оператор **select**. Допустим, у нас есть набор объектов следующего класса, представляющего пользователя:
+
+```cs
+class User
+{
+    public string Name { get;set; }
+    public int Age { get; set; }
+}
+```
+
+Но нам нужен не весь объект, а только его свойство *Name*:
+
+```cs
+List<User> users = new List<User>();
+users.Add(
+    new User { Name = "Sam", Age = 43 });
+users.Add(
+    new User { Name = "Tom", Age = 33 });
+ 
+var names = users.Select(u => u.Name);
+ 
+foreach (string n in names)
+     Console.WriteLine(n);
+```
+
+Результат выражения **LINQ** будет представлять набор строк, поскольку метод `Select(u => u.Name)` выбирают в результирующую выборку только значения свойства *Name*.
+
+Аналогично можно создать объекты другого типа, в том числе анонимного:
+
+```cs
+List<User> users = new List<User>();
+users.Add(
+    new User { Name = "Sam", Age = 43 });
+users.Add(
+    new User { Name = "Tom", Age = 33 });
+ 
+var items = users.Select(u => new { 
+    FirstName = u.Name, 
+    DateOfBirth = DateTime.Now.Year - u.Age 
+});
+ 
+foreach (var n in items)
+    Console.WriteLine($"{n.FirstName} - {n.DateOfBirth}");
+```
+
+Здесь метод _select_ создает объект базового типа (object), используя текущий объект **User**. И теперь результат будет содержать набор объектов данного типа, в котором определены два свойства: *FirstName* и *DateOfBirth*.
+
+### Переменые в запросах и оператор let
+
+Иногда возникает необходимость произвести в запросах **LINQ** какие-то дополнительные промежуточные вычисления. Для этих целей мы можем задать в запросах свои переменные с помощью оператора _let_:
+
+```cs
+List<User> users = new List<User>()
+{
+    new User { 
+        Name = "Sam", Age = 43 },
+    new User { 
+        Name = "Tom", Age = 33 }
+};
+ 
+var people = from u in users
+             let name = "Mr. " + u.Name
+             select new
+             {
+                Name = name,
+                Age = u.Age
+             };
+```
+
+В данном случае создается переменная name, значение которой равно `"Mr. " + u.Name`.
+
+Возможность определения переменных наверное одно из главных преимуществ операторов **LINQ** по сравнению с методами расширения.
+
+### Выборка из нескольких источников
+
+В LINQ можно выбирать объекты не только из одного, но и из большего количества источников:
+
+Например, возьмем классы:
+
+```cs
+class Phone
+{
+    public string Name { get; set; }
+    public string Company { get; set; }
+}
+class User
+{
+    public string Name { get; set; }
+    public int Age { get; set; }
+}
+```
+
+Создадим два разных источника данных и произведем выборку:
+
+```cs
+List<User> users = new List<User>()
+{
+    new User { Name = "Sam", Age = 43 },
+    new User { Name = "Tom", Age = 33 }
+};
+List<Phone> phones = new List<Phone>()
+{
+    new Phone {
+        Name="Lumia 630", Company="Microsoft" },
+    new Phone {
+        Name="iPhone 6", Company="Apple"},
+};
+ 
+var people = from user in users
+             from phone in phones
+             select new { Name = user.Name, Phone = phone.Name };
+ 
+foreach (var p in people)
+    Console.WriteLine($"{p.Name} - {p.Phone}");
+```
+
+Консольный вывод:
+
+```
+Sam - Lumia 630
+Sam - iPhone 6
+Tom - Lumia 630
+Tom - iPhone 6
+```
+
+Таким образом, при выборке из двух источников каждый элемент из первого источника будет сопоставляться с каждым элементом из второго источника. То есть получиться 4 пары.
+
+## Сортировка
+
+Для сортировки набора данных по возрастанию используется метод *OrderBy*:
+
+```cs
+int[] numbers = { 3, 12, 4, 10, 34, 20, 55, -66, 77, 88, 4 };
+IEnumerable<int> orderedNumbers = numbers.OrderBy(i => i);
+foreach (int i in orderedNumbers)
+    Console.WriteLine(i);
+```
+
+Метод *OrderBy* принимает критерий сортировки. В данном случае в качестве критерия выступает само число.
+
+Возьмем посложнее пример. Допустим, надо отсортировать выборку сложных объектов. Тогда в качестве критерия мы можем указать свойство класса объекта:
+
+```cs
+List<User> users = new List<User>()
+{
+    new User { Name = "Tom", Age = 33 },
+    new User { Name = "Bob", Age = 30 },
+    new User { Name = "Tom", Age = 21 },
+    new User { Name = "Sam", Age = 43 }
+};
+ 
+var sortedUsers = users.OrderBy(u => u.Name);
+ 
+foreach (User u in sortedUsers)
+    Console.WriteLine(u.Name);
+```
+
+Метод *OrderBy()* сортирует по возрастанию. Для сортировки по убыванию используется метод *OrderByDescending*:
+
+```cs
+var sortedUsers = users.OrderByDescending(u => u.Name);
+```
+
+### Множественные критерии сортировки
+
+В наборах сложных объектов иногда возникает ситуация, когда надо отсортировать не по одному, а сразу по нескольким полям. Для этого в запросе **LINQ** добавляются методы _ThenBy_ (для сортировки по возрастанию) или _ThenByDescending_ (для сортировки по убыванию):
+
+```cs
+List<User> users = new List<User>()
+{
+    new User { Name = "Tom", Age = 33 },
+    new User { Name = "Bob", Age = 30 },
+    new User { Name = "Tom", Age = 21 },
+    new User { Name = "Sam", Age = 43 }
+};
+
+var result = users
+    .OrderBy(u => u.Name)
+    .ThenBy(u => u.Age);
+
+foreach (User u in result)
+    Console.WriteLine($"{u.Name} - {u.Age}");
+```
+
+Результат программы:
+
+```
+Alice - 28
+Bob - 30
+Sam - 43
+Tom - 21
+Tom - 33
+```
+
+---
+
+## Задание на дом:
+
+1. Реализовать примеры из лекции на своей предметной области (приветствуется использование методов не рассмотренных в рамках лекции: distinct, sum и т.п.).
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Ещё раз про классы. Интерфейсы.](./t6_oop_habr.md) | [Содержание](../readme.md#тема-6-основные-принципы-объектно-ориентированного-программирования) | [Шаблоны проектирования.](./t6_templates.md)

+ 2210 - 0
articles/t6_oop1.md

@@ -0,0 +1,2210 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Регулярные выражения](./t5_regex.md) | [Содержание](../readme.md#тема-6-основные-принципы-объектно-ориентированного-программирования) | [Ещё раз про классы. Интерфейсы.](./t6_oop_habr.md)
+
+# Тема 6. Основные принципы объектно-ориентированного программирования
+
+## История развития ООП
+
+<details>
+
+<summary>Развернуть</summary>
+
+Термины «объектно-» и «ориентированный» в современном смысле этих слов появились в MIT в конце 1950 начале 1960 годов. В среде специалистов по искусственному интеллекту термин «объект» мог относиться к идентифицированным элементам (атомы Lisp) со свойствами (атрибутами). Алан Кэй позже писал, что понимание внутреннего устройства Лиспа оказало серьезное влияние на его мышление в 1966 г. Другим ранним примером ООП в MIT был Sketchpad созданный Иваном Сазерлендом в 1960-61. В глоссарии подготовленного в 1963 г. технического отчета, основанного на его диссертации о Sketchpad, Сазерленд определяет понятия «объект» и «экземпляр» с концепцией классов на основе «мастера» или «определения», хотя все эти термины относились к графическому представлению объектов [вкратце, в Sketchpad было основное изображение, на основе которого строились копии. При изменении основного – копии тоже менялись. Прим. пер.].
+
+В ранней MIT-версии ALGOL AED-0 структуры данных («плексы» на диалекте Алгола) напрямую были связаны с процедурами, которые впоследствии были названы сообщениями, методами или функциями-членами.
+
+Объекты, как формализованный концепт появились в программировании в 1960-х в Simula 67, модернизированной версии Simula I, языка программирования, ориентированного на дискретно-событийное моделирование. Авторы Simula — Оле-Йохан Даль и Кристен Нюгорд из Норвежского компьютерного центра в Осло. Simula разрабатывалась под влиянием SIMSCRIPT и предложенной Чарльзом Хоаром концепцией записей-классов. Simula включала в себя понятие классов и экземпляров (или объектов), а также подклассов, виртуальных методов, сопрограмм и дискретно-событийное моделирование как часть собственной парадигмы программирования. В языке использовался автоматический сборщик мусора, который был изобретен ранее для функционального языка Lisp. Simula использовалась тогда преимущественно для физического моделирования. Идеи Simula оказали серьезное влияние на более поздние языки, такие как Smalltalk, варианты Lisp (CLOS), Object Pascal, и C++.
+
+Язык Smalltalk, который был изобретен в компании Xerox PARC Аланом Кэем (Alan Kay) и некоторыми другими учеными, фактически навязывал использование «объектов» и «сообщений» как базиса для вычислений. Создателей Smalltalk вдохновляли некоторые идеи Simula, но Smalltalk разрабатывался как полностью динамичная система, в которой классы могут создаваться и изменяться динамически, а не только статически как в Simula. Smalltalk и ООП с его помощью были представлены широкой аудитории в журнале Byte magazine в августе 1981.
+
+В 1970-х Smalltalk Кэя сподвиг сообщество Lisp внедрить в язык объектно-ориентированные техники, которые были представлены разработчикам с помощью Lisp машины.
+
+Эксперименты с различными расширениями Lisp в конечном итоге привели к созданию Common Lisp Object System (CLOS, части первого стандартизованного объектно-ориентированного языка, ANSI Common Lisp), который органично включал в себя как функциональное, так и объектно-ориентированное программирование и позволял расширять себя с помощью протокола Meta-object protocol. В 1980 было несколько попыток дизайна архитектур процессоров, которые включали бы в себя аппаратную поддержку работы с объектами в памяти, но все они были безуспешны. В качестве примеров можно привести Intel iAPX 432 и Linn Smart Rekursiv.
+
+Объектно-ориентированное программирование развилось в доминирующую методологию программирования в начале и середине 1990 годов, когда стали широко доступны поддерживающие ее языки программирования, такие как Visual FoxPro 3.0, C++, и Delphi. Доминирование этой системы поддерживалось ростом популярности графических интерфейсов пользователя, которые основывались на техниках ООП. Пример тесной связи между динамической библиотекой GUI и объектно-ориентированного языка программирования можно найти посмотрев на фреймворк Cocoa на Mac OS X, который был написан на Objective-C, объектно-ориентированом расширении к С, основанном на Smalltalk с поддержкой динамических сообщений. Инструментарии ООП повлияли на популярность событийно-ориентированного программирования (хотя, эта концепция не ограничивается одним ООП). Некоторые даже думали, что кажущаяся или реальная связь с графическими интерфейсами – это то, что вынесло ООП на передний план технологий.
+
+В ETH Zürich, Никлаус Вирт и его коллеги тоже исследовали такие предметы, как абстрация данных и модульное программирование, хотя эти подходы широко использовались и в 60-х и ранее. Modula-2 вышедшая в 1978 включала оба эти подхода, а ее последователь Oberon имел собственный подход к объктно-ориентированности, классам и прочему, непохожий на подход Smalltalk и совсем не похожий на подход C++.
+
+Возможности ООП добавлялись во многие языки того времени, включая Ada, BASIC, Fortran, Pascal и другие. Их добавление в языки, изначально не разрабатывавшиеся для поддержки ООП часто приводило к проблемам с совместимостью и поддержкой кода.
+
+Позднее стали появляться языки, поддерживающие как объектно-ориентированный подход, так и процедурный вроде Python и Ruby. Пожалуй, самыми коммерчески успешными объектно-ориентированными языками можно назвать Visual Basic.NET, C# и Java. И .NET и Java демонстрируют превосходство ООП.
+
+---
+
+Объектно-ориентированная идеология разрабатывалась как попытка связать поведение сущности с её данными и спроецировать объекты реального мира и бизнес-процессов в программный код. Задумывалось, что такой код проще читать и понимать человеком, т. к. людям свойственно воспринимать окружающий мир как множество взаимодействующих между собой объектов, поддающихся определенной классификации.
+
+Не следует думать, что ООП каким-то чудным образом ускорит написание программ, и ожидать ситуацию, когда жители Вилларибо уже выкатили ООП-проект в работу, а жители Виллабаджо все еще отмывают жирный спагетти-код. В большинстве случаев это не так, и время экономится не на стадии разработки, а на этапах поддержки (расширение, модификация, отладка и тестирование), то бишь в долгосрочной перспективе. Если вам требуется написать одноразовый скрипт, который не нуждается в последующей поддержке, то и ООП в этой задаче, вероятнее всего, не пригодится. Однако, значительную часть жизненного цикла большинства современных проектов составляют именно поддержка и расширение. Само по себе наличие ООП не делает вашу архитектуру безупречной, и может наоборот привести к излишним усложнениям.
+
+</details>
+
+## Классы и объекты
+
+>Дальше передрано с [метанита](https://metanit.com/sharp/tutorial/3.1.php)
+
+C# является полноценным объектно-ориентированным языком. Это значит, что программу на C# можно представить в виде взаимосвязанных взаимодействующих между собой объектов.
+
+Описанием объекта является класс, а объект представляет экземпляр этого класса. Можно еще провести следующую аналогию. У нас у всех есть некоторое представление о человеке, у которого есть имя, возраст, какие-то другие характеристики. То есть некоторый шаблон - этот шаблон можно назвать классом. Конкретное воплощение этого шаблона может отличаться, например, одни люди имеют одно имя, другие - другое имя. И реально существующий человек (фактически экземпляр данного класса) будет представлять объект этого класса.
+
+По умолчанию проект консольного приложения уже содержит один класс **Program**, с которого и начинается выполнение программы.
+
+По сути класс представляет новый тип, который определяется пользователем. Класс определяется с помощью ключевого слова **сlass**:
+
+```cs
+class Person
+{
+ 
+}
+```
+
+Где определяется класс? Класс можно определять внутри пространства имен, вне пространства имен, внутри другого класса. Как правило, классы помещаются в отдельные файлы.
+
+Пример класса, определенного внутри пространства имён:
+
+```cs
+using System;
+ 
+namespace HelloApp
+{
+    class Person
+    {
+         
+    }
+    class Program
+    {
+        static void Main(string[] args)
+        {
+             
+        }
+    }
+}
+```
+
+Вся функциональность класса представлена его членами - полями (полями называются переменные класса), свойствами, методами, событиями. Например, определим в классе **Person** поля и метод:
+
+```cs
+using System;
+ 
+namespace HelloApp
+{
+    class Person
+    {
+        public string name; // имя
+        public int age = 18;     // возраст
+ 
+        public void GetInfo()
+        {
+            Console.WriteLine($"Имя: {name}  Возраст: {age}");
+        }
+    }
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            Person tom;
+        }
+    }
+}
+```
+
+В данном случае класс **Person** представляет человека. Поле *name* хранит имя, а поле *age* - возраст человека. А метод *GetInfo* выводит все данные на консоль. Чтобы все данные были доступны вне класса **Person** переменные и метод определены с модификатором **public**. Поскольку поля фактически те же переменные, им можно присвоить начальные значения, как в случае выше, поле *age* инициализировано значением `18`.
+
+Так как класс представляет собой новый тип, то в программе мы можем определять переменные, которые представляют данный тип. Так, здесь в методе *Main* определена переменная *tom*, которая представляет класс **Person**. Но пока эта переменная не указывает ни на какой объект и по умолчанию она имеет значение **null**. Поэтому вначале необходимо создать объект класса **Person**.
+
+### Константы класса
+
+Константы характеризуются следующими признаками:
+
+* Константа должна быть проинициализирована при определении
+* После определения значение константы не может быть изменено
+
+Константы предназначены для описания таких значений, которые не должны изменяться в программе. Для определения констант используется ключевое слово **const**:
+
+```cs
+const double PI = 3.14;
+const double E = 2.71;
+```
+
+При использовании констант надо помнить, что объявить мы их можем только один раз и что к моменту компиляции они должны быть определены.
+
+```cs
+class MathLib
+{
+    public const double PI=3.141;
+    public const double E = 2.81;
+    public const double K;      // Ошибка, константа не инициализирована
+}
+ 
+class Program
+{
+    static void Main(string[] args)
+    {
+        MathLib.E=3.8; // Ошибка, значение константы нельзя изменить
+    }
+}
+```
+
+Также обратите внимание на синтаксис обращения к константе. Так как неявно это статическое поле, для обращения к ней необходимо использовать имя класса.
+
+```cs
+class MathLib
+{
+    public const double PI=3.141;
+}
+ 
+class Program
+{
+    static void Main(string[] args)
+    {
+        Console.WriteLine(MathLib.PI);
+    }
+}
+```
+
+Но следует учитывать, что мы не можем объявить константу с модификатором **static**. Но в этом собственно и нет смысла.
+
+### Конструкторы
+
+Кроме обычных методов в классах используются также и специальные методы, которые называются **конструкторами**. **Конструкторы** вызываются при создании нового объекта данного класса. Конструкторы выполняют инициализацию объекта.
+
+### Конструктор по умолчанию
+
+Если в классе не определено ни одного конструктора, то для этого класса автоматически создается конструктор по умолчанию. Такой конструктор не имеет параметров и не имеет тела.
+
+Выше класс **Person** не имеет никаких конструкторов. Поэтому для него автоматически создается конструктор по умолчанию. И мы можем использовать этот конструктор. В частности, создадим один объект класса **Person**:
+
+```cs
+class Person
+{
+    public string name; // имя
+    public int age;     // возраст
+ 
+    public void GetInfo()
+    {
+        Console.WriteLine($"Имя: {name}  Возраст: {age}");
+    }
+}
+
+class Program
+{
+    static void Main(string[] args)
+    {
+        Person tom = new Person();
+        tom.GetInfo();      // Имя: Возраст: 0
+ 
+        tom.name = "Tom";
+        tom.age = 34;
+        tom.GetInfo();  // Имя: Tom Возраст: 34
+        Console.ReadKey();
+    }
+}
+```
+
+Для создания объекта **Person** используется выражение `new Person()`. Оператор **new** выделяет память для объекта **Person**. И затем вызывается конструктор по умолчанию, который не принимает никаких параметров. В итоге после выполнения данного выражения в памяти будет выделен участок, где будут храниться все данные объекта **Person**. А переменная *tom* получит ссылку на созданный объект.
+
+Если конструктор не инициализирует значения переменных объекта, то они получают значения по умолчанию. Для переменных числовых типов это число `0`, а для типа **string** и классов - это значение **null** (то есть фактически отсутствие значения).
+
+После создания объекта мы можем обратиться к переменным объекта **Person** через переменную *tom* и установить или получить их значения, например, `tom.name = "Tom";`.
+
+Консольный вывод данной программы:
+
+```
+Имя:	Возраст: 0
+Имя: Tom	Возраст: 34
+```
+
+### Создание конструкторов
+
+Выше для инициализации объекта использовался конструктор по умолчанию. Однако мы сами можем определить свои конструкторы:
+
+```cs
+class Person
+{
+    public string name;
+    public int age;
+ 
+    // 1 конструктор
+    public Person() { 
+        name = "Неизвестно"; 
+        age = 18; 
+    }      
+     
+    // 2 конструктор 
+    public Person(string n) { 
+        name = n; 
+        age = 18; 
+    }         
+     
+    // 3 конструктор 
+    public Person(string n, int a) { 
+        name = n; 
+        age = a; 
+    }   
+}
+```
+
+Теперь в классе определено три конструктора, каждый из которых принимает различное количество параметров и устанавливает значения полей класса.
+
+Используем эти конструкторы:
+
+```cs
+static void Main(string[] args)
+{
+    // вызов 1-ого конструктора без параметров
+    Person tom = new Person();          
+
+    //вызов 2-ого конструктора с одним параметром
+    Person bob = new Person("Bob");     
+
+    // вызов 3-его конструктора с двумя параметрами
+    Person sam = new Person("Sam", 25); 
+     
+    tom.GetInfo();          // Имя: Неизвестно  Возраст: 18
+    bob.GetInfo();          // Имя: Bob  Возраст: 18
+    sam.GetInfo();          // Имя: Sam  Возраст: 25
+}
+```
+
+Консольный вывод данной программы:
+
+```
+Имя: Неизвестно  Возраст: 18
+Имя: Bob  Возраст: 18
+Имя: Sam  Возраст: 25
+```
+
+При этом если в классе определены конструкторы, то при создании объекта **необходимо** использовать один из этих конструкторов.
+
+Стоит отметить, что начиная с версии C# 9.0 мы можем сократить вызов конструктора, убрав из него название типа:
+
+```cs
+// аналогично new Person();
+Person tom = new ();       
+
+// аналогично new Person("Bob");
+Person bob = new ("Bob");       
+
+// аналогично new Person("Sam", 25);
+Person sam = new ("Sam", 25);   
+```
+
+### Ключевое слово **this**
+
+Ключевое слово **this** представляет ссылку на текущий экземпляр класса. В каких ситуациях оно нам может пригодиться? В примере выше определены три конструктора. Все три конструктора выполняют однотипные действия - устанавливают значения полей *name* и *age*. Но этих повторяющихся действий могло быть больше. И мы можем не дублировать функциональность конструкторов, а просто обращаться из одного конструктора к другому через ключевое слово **this**, передавая нужные значения для параметров:
+
+```cs
+class Person
+{
+    public string name;
+    public int age;
+ 
+    public Person() : this("Неизвестно")
+    {
+    }
+    public Person(string name) : this(name, 18)
+    {
+    }
+    public Person(string name, int age)
+    {
+        this.name = name;
+        this.age = age;
+    }
+    public void GetInfo()
+    {
+        Console.WriteLine($"Имя: {name}  Возраст: {age}");
+    }
+}
+```
+
+В данном случае первый конструктор вызывает второй, а второй конструктор вызывает третий. По количеству и типу параметров компилятор узнает, какой именно конструктор вызывается. Например, во втором конструкторе:
+
+```cs
+public Person(string name) : this(name, 18)
+{
+}
+```
+
+идет обращение к третьему конструктору, которому передаются два значения. Причем в начале будет выполняться именно третий конструктор, и только потом код второго конструктора.
+
+Также стоит отметить, что в третьем конструкторе параметры называются также, как и поля класса.
+
+```cs
+public Person(string name, int age)
+{
+    this.name = name;
+    this.age = age;
+}
+```
+
+И чтобы разграничить параметры и поля класса, к полям класса обращение идет через ключевое слово **this**. Так, в выражении `this.name = name`; первая часть *this.name* означает, что *name* - это поле текущего класса, а не название параметра *name*. Если бы у нас параметры и поля назывались по-разному, то использовать слово **this** было бы необязательно. Также через ключевое слово **this** можно обращаться к любому полю или методу.
+
+### Инициализаторы объектов
+
+Для инициализации объектов классов можно применять инициализаторы. Инициализаторы представляют передачу в фигурных скобках значений доступным полям и свойствам объекта:
+
+```cs
+Person tom = new Person { name = "Tom", age=31 };
+tom.GetInfo();          // Имя: Tom  Возраст: 31
+```
+
+С помощью инициализатора объектов можно присваивать значения всем доступным полям и свойствам объекта в момент создания без явного вызова конструктора.
+
+При использовании инициализаторов следует учитывать следующие моменты:
+
+* С помощью инициализатора мы можем установить значения только доступных из внешнего кода полей и свойств объекта. Например, в примере выше поля *name* и *age* имеют модификатор доступа **public**, поэтому они доступны из любой части программы.
+
+* Инициализатор выполняется после конструктора, поэтому если и в конструкторе, и в инициализаторе устанавливаются значения одних и тех же полей и свойств, то значения, устанавливаемые в конструкторе, заменяются значениями из инициализатора.
+
+## Структуры
+
+<details>
+
+<summary>Развернуть</summary>
+
+Наряду с классами **структуры** представляют еще один способ создания собственных типов данных в C#. Более того многие примитивные типы, например, **int**, **double** и т.д., по сути являются структурами.
+
+Например, определим структуру, которая представляет человека:
+
+```cs
+struct User
+{
+    public string name;
+    public int age;
+ 
+    public void DisplayInfo()
+    {
+        Console.WriteLine($"Name: {name}  Age: {age}");
+    }
+}
+```
+
+Как и классы, структуры могут хранить состояние в виде переменных и определять поведение в виде методов. Так, в данном случае определены две переменные - *name* и *age* для хранения соответственно имени и возраста человека и метод *DisplayInfo* для вывода информации о человеке.
+
+Используем эту структуру в программе:
+
+```cs
+using System;
+ 
+namespace HelloApp
+{ 
+    struct User
+    {
+        public string name;
+        public int age;
+ 
+        public void DisplayInfo()
+        {
+            Console.WriteLine($"Name: {name}  Age: {age}");
+        }
+    }
+ 
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            User tom;
+            tom.name = "Tom";
+            tom.age = 34;
+            tom.DisplayInfo();
+             
+            Console.ReadKey();
+        }
+    }
+}
+```
+
+В данном случае создается объект *tom*. У него устанавливаются значения глобальных переменных, и затем выводится информация о нем.
+
+### Конструкторы структуры
+
+Как и класс, структура может определять конструкторы. Но в отличие от класса нам не обязательно вызывать конструктор для создания объекта структуры:
+
+```cs
+User tom;
+```
+
+Однако если мы таким образом создаем объект структуры, то обязательно надо проинициализировать все поля (глобальные переменные) структуры перед получением их значений или перед вызовом методов структуры. То есть, например, в следующем случае мы получим ошибку, так как обращение к полям и методам происходит до присвоения им начальных значений:
+
+```cs
+User tom;
+int x = tom.age;    // Ошибка
+tom.DisplayInfo();  // Ошибка
+```
+
+Также мы можем использовать для создания структуры конструктор без параметров, который есть в структуре по умолчанию и при вызове которого полям структуры будет присвоено значение по умолчанию (например, для числовых типов это число 0):
+
+```cs
+User tom = new User();
+tom.DisplayInfo();  // Name:   Age: 0
+```
+
+Обратите внимание, что при использовании конструктора по умолчанию нам не надо явным образом иницилизировать поля структуры.
+
+Также мы можем определить свои конструкторы. Например, изменим структуру User:
+
+```cs
+using System;
+ 
+namespace HelloApp
+{
+    struct User
+    {
+        public string name;
+        public int age;
+        public User(string name, int age)
+        {
+            this.name = name;
+            this.age = age;
+        }
+        public void DisplayInfo()
+        {
+            Console.WriteLine($"Name: {name}  Age: {age}");
+        }
+    }
+ 
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            User tom = new User("Tom", 34);
+            tom.DisplayInfo();
+ 
+            User bob = new User();
+            bob.DisplayInfo();
+             
+            Console.ReadKey();
+        }
+    }
+}
+```
+
+Важно учитывать, что если мы определяем конструктор в структуре, то он **должен инициализировать все поля структуры**, как в данном случае устанавливаются значения для переменных *name* и *age*.
+
+Также, как и для класса, можно использовать инициализатор для создания структуры:
+
+```cs
+User person = new User { name = "Sam", age = 31 };
+```
+
+Но в отличие от класса нельзя инициализировать поля структуры напрямую при их объявлении, например, следующим образом:
+
+```cs
+struct User
+{
+    public string name = "Sam";     // ! Ошибка
+    public int age = 23;            // ! Ошибка
+    public void DisplayInfo()
+    {
+        Console.WriteLine($"Name: {name}  Age: {age}");
+    }
+}
+```
+</details>
+
+## Пространства имен, псевдонимы и статический импорт
+
+### Пространства имен
+
+Все определяемые классы и структуры, как правило, не существуют сами по себе, а заключаются в специальные контейнеры - **пространства имен**. Создаваемый по умолчанию класс **Program** уже находится в пространстве имен, которое обычно совпадает с названием проекта:
+
+```cs
+namespace HelloApp
+{  
+    class Program  
+    {
+        static void Main(string[] args) 
+        {
+        }
+    }
+}
+```
+
+Пространство имен определяется с помощью ключевого слова **namespace**, после которого идет название. Так в данном случае полное название класса **Program** будет **HelloApp.Program**.
+
+Класс **Program** видит все классы, которые объявлены в том же пространстве имен:
+
+```cs
+namespace HelloApp
+{  
+    class Program  
+    {
+        static void Main(string[] args) 
+        {
+             Account account = new Account(4);
+        }
+    }
+    class Account
+    {
+        public int Id { get; private set;} // номер счета
+        public Account(int _id)
+        {
+            Id = _id;
+        }
+    }
+}
+```
+
+Но чтобы задействовать классы из других пространств имен, эти пространства надо подключить с помощью директивы **using**:
+
+```cs
+using System;
+namespace HelloApp
+{  
+    class Program  
+    {
+        static void Main(string[] args) 
+        {
+            Console.WriteLine("hello");
+        }
+    }
+}
+```
+
+Здесь подключается пространство имен **System**, в котором определен класс **Console**. Иначе нам бы пришлось писать полный путь к классу:
+
+```cs
+static void Main(string[] args) 
+{
+    System.Console.WriteLine("hello");
+}
+```
+
+Пространства имен могут быть определены внутри других пространств:
+
+```cs
+using HelloApp.AccountSpace;
+namespace HelloApp
+{  
+    class Program  
+    {
+        static void Main(string[] args) 
+        {
+            Account account = new Account(4);
+        }
+    }
+ 
+    namespace AccountSpace
+    {
+        class Account
+        {
+            public int Id { get; private set;}
+            public Account(int _id)
+            {
+                Id = _id;
+            }
+        }
+    } 
+}
+```
+
+В этом случае для подключения пространства указывается его полный путь с учетом внешних пространств имен: `using HelloApp.AccountSpace;`
+
+### Псевдонимы
+
+Для различных классов мы можем использовать псевдонимы. Затем в программе вместо названия класса используется его псевдоним. Например, для вывода строки на экран применяется метод *Console.WriteLine()*. Но теперь зададим для класса **Console** псевдоним:
+
+```cs
+using printer = System.Console;
+class Program
+{
+    static void Main(string[] args)
+    {
+        printer.WriteLine("Hello from C#");
+        printer.Read();
+    }
+}
+```
+
+С помощью выражения `using printer = System.Console` указываем, что псевдонимом для класса **System.Console** будет имя *printer*. Это выражение не имеет ничего общего с подключением пространств имен в начале файла, хотя и использует оператор **using**. При этом используется полное имя класса с учетом пространства имен, в котором класс определен. И далее, чтобы вывести строку, применяется выражение `printer.WriteLine("Hello from C#")`.
+
+И еще пример. Определим класс и для него псевдоним:
+
+```cs
+using Person = HelloApp.User;
+using Printer = System.Console;
+namespace HelloApp
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            Person person = new Person();
+            person.name = "Tom";
+            Printer.WriteLine(person.name);
+            Printer.Read();
+        }
+    }
+ 
+    class User
+    {
+        public string name;
+    }
+}
+```
+
+Класс называется **User**, но в программе для него используется псевдоним **Person**.
+
+Также в C# имеется возможность импорта функциональности классов. Например, импортируем возможности класса **Console**:
+
+```cs
+using static System.Console;
+namespace HelloApp
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            WriteLine("Hello from C# 8.0");
+            Read();
+        }
+    }
+}
+```
+
+Выражение `using static` подключает в программу все статические методы и свойства, а также константы. И после этого мы можем не указывать название класса при вызове метода.
+
+Подобным образом можно определять свои классы и импортировать их:
+
+```cs
+using static System.Console;
+using static System.Math;
+using static HelloApp.Geometry;
+namespace HelloApp
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            double radius = 50;
+            double result = GetArea(radius); //Geometry.GetArea
+            WriteLine(result); //Console.WriteLine
+            Read(); // Console.Read
+        }
+    }
+ 
+    class Geometry
+    {
+        public static double GetArea(double radius)
+        {
+            return PI * radius * radius; // Math.PI
+        }
+    }
+}
+```
+
+## Модификаторы доступа
+
+Все члены класса - поля, методы, свойства - все они имеют **модификаторы доступа**. Модификаторы доступа позволяют задать допустимую область видимости для членов класса. То есть модификаторы доступа определяют контекст, в котором можно употреблять данную переменную или метод. В предыдущих темах мы уже с ним сталкивались, когда объявляли поля класса публичными (то есть с модификатором **public**).
+
+В C# применяются следующие модификаторы доступа:
+
+* **public**: публичный, общедоступный класс или член класса. Такой член класса доступен из любого места в коде, а также из других программ и сборок.
+
+* **private**: закрытый класс или член класса. Представляет полную противоположность модификатору **public**. Такой закрытый класс или член класса доступен только из кода в том же классе или контексте.
+
+* **protected**: такой член класса доступен из любого места в текущем классе или в производных классах. При этом производные классы могут располагаться в других сборках.
+
+<details>
+
+<summary>Развернуть</summary>
+
+* **internal**: класс и члены класса с подобным модификатором доступны из любого места кода в той же сборке, однако он недоступен для других программ и сборок (как в случае с модификатором **public**).
+
+* **protected internal**: совмещает функционал двух модификаторов. Классы и члены класса с таким модификатором доступны из текущей сборки и из производных классов.
+
+* **private protected**: такой член класса доступен из любого места в текущем классе или в производных классах, которые определены в той же сборке.
+
+Мы можем явно задать модификатор доступа, например:
+
+```cs
+private protected class State
+{
+    internal int a;
+    protected void Print() 
+    { 
+        Console.WriteLine($"a = {a}"); 
+    }
+}
+```
+
+Либо можем не указывать:
+
+```cs
+class State
+{
+    int a;
+    void Print() 
+    { 
+        Console.WriteLine($"a = {a}"); 
+    }
+}
+```
+
+Если для полей и методов не определен модификатор доступа, то по умолчанию для них применяется модификатор **private**.
+
+Классы и структуры, объявленные без модификатора, по умолчанию имеют доступ **internal**.
+
+Все классы и структуры, определенные напрямую в пространствах имен и не являющиеся вложенными в другие классы, могут иметь только модификаторы **public** или **internal**.
+
+Посмотрим на примере и создадим следующий класс **State**:
+
+```cs
+public class State
+{
+    // все равно, что private int defaultVar;
+    int defaultVar;
+
+    // поле доступно только из текущего класса
+    private int privateVar;
+
+    // доступно из текущего класса и производных классов, которые определены в этом же проекте
+    protected private int protectedPrivateVar;
+
+    // доступно из текущего класса и производных классов
+    protected int protectedVar;
+
+    // доступно в любом месте текущего проекта
+    internal int internalVar;
+
+    // доступно в любом месте текущего проекта и из классов-наследников в других проектах
+    protected internal int protectedInternalVar;
+
+    // доступно в любом месте программы, а также для других программ и сборок
+    public int publicVar;
+ 
+    // по умолчанию имеет модификатор private
+    void defaultMethod() => Console.WriteLine($"defaultVar = {defaultVar}");
+ 
+    // метод доступен только из текущего класса
+    private void privateMethod() => Console.WriteLine($"privateVar = {privateVar}");
+ 
+    // доступен из текущего класса и производных классов, которые определены в этом же проекте
+    protected private void protectedPrivateMethod() => Console.WriteLine($"protectedPrivateVar = {protectedPrivateVar}");
+ 
+    // доступен из текущего класса и производных классов
+    protected void protectedMethod()=> Console.WriteLine($"protectedVar = {protectedVar}");
+     
+    // доступен в любом месте текущего проекта
+    internal void internalMethod() => Console.WriteLine($"internalVar = {internalVar}");
+     
+    // доступен в любом месте текущего проекта и из классов-наследников в других проектах
+    protected internal void protectedInternalMethod() => Console.WriteLine($"protectedInternalVar = {protectedInternalVar}");
+     
+    // доступен в любом месте программы, а также для других программ и сборок
+    public void publicMethod() => Console.WriteLine($"publicVar = {publicVar}");
+}
+```
+
+Так как класс **State** объявлен с модификатором **public**, он будет доступен из любого места программы, а также из других программ и сборок. Класс **State** имеет пять полей для каждого уровня доступа. Плюс одна переменная без модификатора, которая является закрытой (**private**) по умолчанию.
+
+Также имеются шесть методов, которые будут выводить значения полей класса на экран. Обратите внимание, что так как все модификаторы позволяют использовать члены класса внутри данного класса, то и все переменные класса, в том числе закрытые, у нас доступны всем его методам, так как все находятся в контексте класса **State**.
+
+Теперь посмотрим, как мы сможем использовать переменные нашего класса в программе (то есть в методе *Main* класса **Program**), если классы **State** и **Program** находятся в одном проекте:
+
+```cs
+class Program
+{
+    static void Main(string[] args)
+    {
+        State state1 = new State();
+ 
+        // присвоить значение переменной defaultVar у нас не получится,
+        // так как она имеет модификатор private и класс Program ее не видит
+        // И данную строку среда подчеркнет как неправильную
+        state1.defaultVar = 5; //Ошибка, получить доступ нельзя
+ 
+        // то же самое относится и к переменной privateVar
+        state1.privateVar = 5; // Ошибка, получить доступ нельзя
+ 
+        // присвоить значение переменной protectedPrivateVar не получится,
+        // так как класс Program не является классом-наследником класса State
+        state1.protectedPrivateVar =5; // Ошибка, получить доступ нельзя
+ 
+        // присвоить значение переменной protectedVar тоже не получится,
+        // так как класс Program не является классом-наследником класса State
+        state1.protectedVar = 5; // Ошибка, получить доступ нельзя
+ 
+        // переменная internalVar с модификатором internal доступна из любого места текущего проекта
+        // поэтому спокойно присваиваем ей значение
+        state1.internalVar = 5;
+ 
+        // переменная protectedInternalVar так же доступна из любого места текущего проекта
+        state1.protectedInternalVar = 5;
+ 
+        // переменная publicVar общедоступна
+        state1.publicVar = 5;
+    }
+}
+```
+
+Таким образом, мы смогли установить только переменные *internalVar*, *protectedInternalVar* и *publicVar*, так как их модификаторы позволяют использовать в данном контексте.
+
+Аналогично дело обстоит и с методами:
+
+```cs
+class Program
+{
+    static void Main(string[] args)
+    {
+        State state1 = new State();
+ 
+        state1.defaultMethod(); //Ошибка, получить доступ нельзя
+ 
+        state1.privateMethod(); // Ошибка, получить доступ нельзя
+ 
+        state1.protectedPrivateMethod(); // Ошибка, получить доступ нельзя
+ 
+        state1.protectedMethod(); // Ошибка, получить доступ нельзя
+ 
+        state1.internalMethod();    // норм
+ 
+        state1.protectedInternalMethod();  // норм
+ 
+        state1.publicMethod();      // норм
+    }
+}
+```
+
+Здесь нам оказались доступны только три метода: *internalMethod*, *protectedInternalMethod*, *publicMethod*, которые имееют соответственно модификаторы **internal**, **protected internal**, **public**.
+
+Благодаря такой системе модификаторов доступа можно скрывать некоторые моменты реализации класса от других частей программы.
+
+Несмотря на то, что модификаторы **public** и **internal** похожи по своему действию, но они имеют большое отличие. Классы и члены класса с модификатором **public** также будут доступны и другим программам, если данный класс поместить в динамическую библиотеку dll и потом ее использовать в этих программах.
+</details>
+
+## Свойства
+
+Кроме обычных методов в языке C# предусмотрены специальные методы доступа, которые называют **свойства**. Они обеспечивают простой доступ к полям классов и структур, узнать их значение или выполнить их установку.
+
+Стандартное описание свойства имеет следующий синтаксис:
+
+```
+[модификатор_доступа] возвращаемый_тип произвольное_название
+{
+    // код свойства
+}
+```
+
+Например:
+
+```cs
+class Person
+{
+    private string name;
+ 
+    public string Name
+    {
+        get
+        {
+            return name;
+        }
+ 
+        set
+        {
+            name = value;
+        }
+    }
+}
+```
+
+Здесь у нас есть закрытое поле *name* и есть общедоступное свойство *Name*. Хотя они имеют практически одинаковое название за исключением регистра, но это не более чем стиль, названия у них могут быть произвольные и не обязательно должны совпадать.
+
+Через это свойство мы можем управлять доступом к переменной *name*. Стандартное определение свойства содержит блоки **get** и **set**. В блоке **get** мы возвращаем значение поля, а в блоке **set** устанавливаем. Параметр **value** представляет присваиваемое значение.
+
+Мы можем использовать данное свойство следующим образом:
+
+```cs
+Person p = new Person();
+ 
+// Устанавливаем свойство - срабатывает блок Set
+// значение "Tom" и есть передаваемое в свойство value
+p.Name = "Tom";
+ 
+// Получаем значение свойства и присваиваем его переменной - срабатывает блок Get
+string personName = p.Name; 
+```
+
+Возможно, может возникнуть вопрос, зачем нужны свойства, если мы можем в данной ситуации обходиться обычными полями класса? Но свойства позволяют вложить дополнительную логику, которая может быть необходима, например, при присвоении переменной класса какого-либо значения. Например, нам надо установить проверку по возрасту:
+
+```cs
+class Person
+{
+    private int age;
+ 
+    public int Age
+    {
+        set
+        {
+            if (value < 18)
+            {
+                Console.WriteLine("Возраст должен быть больше 17");
+            }
+            else
+            {
+                age = value;
+            }
+        }
+        get { return age; }
+    }
+}
+```
+
+Если бы переменная *age* была бы публичной, то мы могли бы передать ей извне любое значение, в том числе отрицательное. Свойство же позволяет скрыть данные объекты и опосредовать к ним доступ.
+
+Блоки **set** и **get** не обязательно одновременно должны присутствовать в свойстве. Если свойство определяют только блок **get**, то такое свойство доступно только для чтения - мы можем получить его значение, но не установить. И, наоборот, если свойство имеет только блок **set**, тогда это свойство доступно только для записи - можно только установить значение, но нельзя получить:
+
+```cs
+class Person
+{
+    private string name;
+    // свойство только для чтения
+    public string Name
+    {
+        get
+        {
+            return name;
+        }
+    }
+ 
+    private int age;
+    // свойство только для записи
+    public int Age
+    {
+        set
+        {
+            age = value;
+        }
+    }
+}
+```
+
+Хотя в примерах выше свойства определялись в классе, но точно также мы можем определять и использовать свойства в структурах.
+
+### Модификаторы доступа
+
+<details>
+
+<summary>Развернуть</summary>
+
+Мы можем применять модификаторы доступа не только ко всему свойству, но и к отдельным блокам - либо get, либо set:
+
+```cs
+class Person
+{
+    private string name;
+ 
+    public string Name
+    {
+        get
+        {
+            return name;
+        }
+ 
+        private set
+        {
+            name = value;
+        }
+    }
+    public Person(string name)
+    {
+        Name = name;
+    }
+}
+```
+
+Теперь закрытый блок **set** мы сможем использовать только в данном классе - в его методах, свойствах, конструкторе, но никак не в другом классе:
+
+```cs
+Person p = new Person("Tom");
+ 
+// Ошибка - set объявлен с модификатором private
+//p.Name = "John";
+ 
+Console.WriteLine(p.Name);
+```
+
+При использовании модификаторов в свойствах следует учитывать ряд ограничений:
+
+* Модификатор для блока **set** или **get** можно установить, если свойство имеет оба блока (и set, и get)
+
+* Только один блок **set** или **get** может иметь модификатор доступа, но не оба сразу
+
+* Модификатор доступа блока **set** или **get** должен быть более ограничивающим, чем модификатор доступа свойства. Например, если свойство имеет модификатор **public**, то блок set/get может иметь только модификаторы **protected internal**, **internal**, **protected**, **private**
+</details>
+
+### Автоматические свойства
+
+Свойства управляют доступом к полям класса. Однако что, если у нас с десяток и более полей, то определять каждое поле и писать для него однотипное свойство было бы утомительно. Поэтому в фреймворк .NET были добавлены автоматические свойства. Они имеют сокращенное объявление:
+
+```cs
+class Person
+{
+    public string Name { get; set; }
+    public int Age { get; set; }
+         
+    public Person(string name, int age)
+    {
+        Name = name;
+        Age = age;
+    }
+}
+```
+
+На самом деле тут также создаются поля для свойств, только их создает не программист в коде, а компилятор автоматически генерирует при компиляции.
+
+В чем преимущество автосвойств, если по сути они просто обращаются к автоматически создаваемой переменной, почему бы напрямую не обратиться к переменной без автосвойств? Дело в том, что в любой момент времени при необходимости мы можем развернуть автосвойство в обычное свойство, добавить в него какую-то определенную логику.
+
+Стоит учитывать, что нельзя создать автоматическое свойство только для записи, как в случае со стандартными свойствами.
+
+Автосвойствам можно присвоить значения по умолчанию (инициализация автосвойств):
+
+```cs
+class Person
+{
+    public string Name { get; set; } = "Tom";
+    public int Age { get; set; } = 23;
+}
+     
+class Program
+{
+    static void Main(string[] args)
+    {
+        Person person = new Person();
+        Console.WriteLine(person.Name); // Tom
+        Console.WriteLine(person.Age);  // 23
+         
+        Console.Read();
+    }
+}
+```
+
+И если мы не укажем для объекта **Person** значения свойств *Name* и *Age*, то будут действовать значения по умолчанию.
+
+Стоит отметить, что в структурах мы не можем использовать инициализацию автосвойств.
+
+Автосвойства также могут иметь модификаторы доступа:
+
+```cs
+class Person
+{
+    public string Name { private set; get;}
+    public Person(string n)
+    {
+        Name = n;
+    }
+}
+```
+
+Мы можем убрать блок **set** и сделать автосвойство доступным только для чтения. В этом случае для хранения значения этого свойства для него неявно будет создаваться поле с модификатором readonly, поэтому следует учитывать, что подобные get-свойства можно установить либо из конструктора класса, как в примере выше, либо при инициализации свойства:
+
+```cs
+class Person
+{
+    public string Name { get;} = "Tom"
+}
+```
+
+### Сокращенная запись свойств
+
+Как и методы, мы можем сокращать свойства. Например:
+
+```cs
+class Person
+{
+    private string name;
+     
+    // эквивалентно public string Name { get { return name; } }
+    public string Name => name;
+}
+```
+
+## Перегрузка методов
+
+Иногда возникает необходимость создать один и тот же метод, но с разным набором параметров. И в зависимости от имеющихся параметров применять определенную версию метода. Такая возможность еще называется перегрузкой методов (method overloading).
+
+И в языке C# мы можем создавать в классе несколько методов с одним и тем же именем, но разной сигнатурой. Что такое сигнатура? Сигнатура складывается из следующих аспектов:
+
+* Имя метода
+
+* Количество параметров
+
+* Типы параметров
+
+* Порядок параметров
+
+* Модификаторы параметров
+
+Но названия параметров в сигнатуру НЕ входят. Например, возьмем следующий метод:
+
+```cs
+public int Sum(int x, int y) 
+{ 
+    return x + y;
+}
+```
+
+У данного метода сигнатура будет выглядеть так: Sum(int, int)
+
+И перегрузка метода как раз заключается в том, что методы имеют разную сигнатуру, в которой совпадает только название метода. То есть методы должны отличаться по:
+
+* Количеству параметров
+
+* Типу параметров
+
+* Порядку параметров
+
+* Модификаторам параметров
+
+Например, пусть у нас есть следующий класс:
+
+```cs
+class Calculator
+{
+    public void Add(int a, int b)
+    {
+        int result = a + b;
+        Console.WriteLine($"Result is {result}");
+    }
+    public void Add(int a, int b, int c)
+    {
+        int result = a + b + c;
+        Console.WriteLine($"Result is {result}");
+    }
+    public int Add(int a, int b, int c, int d)
+    {
+        int result = a + b + c + d;
+        Console.WriteLine($"Result is {result}");
+        return result;
+    }
+    public void Add(double a, double b)
+    {
+        double result = a + b;
+        Console.WriteLine($"Result is {result}");
+    }
+}
+```
+
+Здесь представлены четыре разных версии метода *Add*, то есть определены четыре перегрузки данного метода.
+
+Первые три версии метода отличаются по количеству параметров. Четвертая версия совпадает с первой по количеству параметров, но отличается по их типу. При этом достаточно, чтобы хотя бы один параметр отличался по типу. Поэтому это тоже допустимая перегрузка метода Add.
+
+То есть мы можем представить сигнатуры данных методов следующим образом:
+
+```
+Add(int, int)
+Add(int, int, int)
+Add(int, int, int, int)
+Add(double, double)
+```
+
+После определения перегруженных версий мы можем использовать их в программе:
+
+```cs
+class Program
+{
+    static void Main(string[] args)
+    {
+        Calculator calc = new Calculator();
+        calc.Add(1, 2); // 3
+        calc.Add(1, 2, 3); // 6
+        calc.Add(1, 2, 3, 4); // 10
+        calc.Add(1.4, 2.5); // 3.9
+         
+        Console.ReadKey();
+    }
+}
+```
+
+Консольный вывод:
+
+```
+Result is 3
+Result is 6
+Result is 10
+Result is 3.9
+```
+
+Также перегружаемые методы могут отличаться по используемым модификаторам. Например:
+
+```cs
+void Increment(ref int val)
+{
+    val++;
+    Console.WriteLine(val);
+}
+ 
+void Increment(int val)
+{
+    val++;
+    Console.WriteLine(val);
+}
+```
+
+В данном случае обе версии метода *Increment* имеют одинаковый набор параметров одинакового типа, однако в первом случае параметр имеет модификатор **ref**. Поэтому обе версии метода будут корректными перегрузками метода *Increment*.
+
+А отличие методов по **возвращаемому типу** или по **имени параметров** не является основанием для перегрузки. Например, возьмем следующий набор методов:
+
+```cs
+int Sum(int x, int y)
+{
+    return x + y;
+}
+int Sum(int number1, int number2)
+{
+    return x + y;
+}
+void Sum(int x, int y)
+{
+    Console.WriteLine(x + y);
+}
+```
+
+Сигнатура у всех этих методов будет совпадать:
+
+```
+Sum(int, int)
+```
+
+Поэтому данный набор методов не представляет корректные перегрузки метода Sum и работать не будет.
+
+## Статические члены и модификатор static
+
+Кроме обычных полей, методов, свойств класс может иметь статические поля, методы, свойства. Статические поля, методы, свойства относятся ко всему классу и для обращения к подобным членам класса необязательно создавать экземпляр класса. Например:
+
+```cs
+class Account
+{
+    public static decimal bonus = 100;
+    public decimal totalSum;
+    public Account(decimal sum)
+    {
+        totalSum = sum + bonus; 
+    }
+}
+class Program
+{
+    static void Main(string[] args)
+    {
+        Console.WriteLine(Account.bonus);      // 100
+        Account.bonus += 200;
+         
+        Account account1 = new Account(150);
+        Console.WriteLine(account1.totalSum);   // 450
+ 
+ 
+        Account account2 = new Account(1000);
+        Console.WriteLine(account2.totalSum);   // 1300
+ 
+        Console.ReadKey();
+    }
+}
+```
+
+В данном случае класс **Account** имеет два поля: *bonus* и *totalSum*. Поле *bonus* является статическим, поэтому оно хранит состояние класса в целом, а не отдельного объекта. И поэтому мы можем обращаться к этому полю по имени класса:
+
+```cs
+Console.WriteLine(Account.bonus);
+Account.bonus += 200;
+```
+
+На уровне памяти для статических полей будет создаваться участок в памяти, который будет общим для всех объектов класса.
+
+При этом **память для статических переменных выделяется даже в том случае, если не создано ни одного объекта этого класса**.
+
+## Статические свойства и методы
+
+Подобным образом мы можем создавать и использовать статические методы и свойства:
+
+```cs
+class Account
+{
+    public Account(decimal sum, decimal rate)
+    {
+        if (sum < MinSum) throw new Exception("Недопустимая сумма!");
+        Sum = sum; Rate = rate;
+    }
+
+    private static decimal minSum = 100; // минимальная допустимая сумма для всех счетов
+    public static decimal MinSum
+    {
+        get { return minSum; }
+        set { if(value>0) minSum = value; }
+    }
+ 
+    public decimal Sum { get; private set; }    // сумма на счете
+    public decimal Rate { get; private set; }   // процентная ставка
+ 
+    // подсчет суммы на счете через определенный период по определенной ставке
+    public static decimal GetSum(decimal sum, decimal rate, int period)
+    {
+        decimal result = sum;
+        for (int i = 1; i <= period; i++)
+            result = result + result * rate / 100;
+        return result;
+    }
+}
+```
+
+Переменная *minSum*, свойство *MinSum*, а также метод *GetSum* здесь определены с ключевым словом **static**, то есть они являются статическими.
+
+Переменная *minSum* и свойство *MinSum* представляют минимальную сумму, которая допустима для создания счета. Этот показатель не относится к какому-то конкретному счету, а относится ко всем счетам в целом. Если мы изменим этот показатель для одного счета, то он также должен измениться и для другого счета. То есть в отличии от свойств *Sum* и *Rate*, которые хранят состояние объекта, переменная *minSum* хранит состояние для всех объектов данного класса.
+
+То же самое с методом *GetSum* - он вычисляет сумму на счете через определенный период по определенной процентной ставке для определенной начальной суммы. Вызов и результат этого метода не зависит от конкретного объекта или его состояния.
+
+Таким образом, переменные и свойства, которые хранят состояние, общее для всех объектов класса, следует определять как статические. И также методы, которые определяют общее для всех объектов поведение, также следует объявлять как статические.
+
+Статические члены класса являются общими для всех объектов этого класса, поэтому к ним надо обращаться по имени класса:
+
+```cs
+Account.MinSum = 560;
+decimal result = Account.GetSum(1000, 10, 5);
+```
+
+Следует учитывать, что статические методы могут обращаться только к статическим членам класса. Обращаться к нестатическим методам, полям, свойствам внутри статического метода мы не можем.
+
+Нередко статические поля применяются для хранения счетчиков. Например, пусть у нас есть класс **User**, и мы хотим иметь счетчик, который позволял бы узнать, сколько объектов **User** создано:
+
+```cs
+class User
+{
+    private static int counter = 0;
+    public User()
+    {
+        counter++;
+    }
+ 
+    public static void DisplayCounter()
+    {
+        Console.WriteLine($"Создано {counter} объектов User");
+    }
+}
+class Program
+{
+    static void Main(string[] args)
+    {
+        User user1 = new User();
+        User user2 = new User();
+        User user3 = new User();
+        User user4 = new User();
+        User user5 = new User();
+         
+        User.DisplayCounter(); // 5
+ 
+        Console.ReadKey();
+    }
+}
+```
+
+### Статический конструктор
+
+Кроме обычных конструкторов у класса также могут быть статические конструкторы. Статические конструкторы имеют следующие отличительные черты:
+
+* Статические конструкторы не должны иметь модификатор доступа и не принимают параметров
+
+* Как и в статических методах, в статических конструкторах нельзя использовать ключевое слово **this** для ссылки на текущий объект класса и можно обращаться только к статическим членам класса
+
+* Статические конструкторы нельзя вызвать в программе вручную. Они выполняются автоматически при самом первом создании объекта данного класса или при первом обращении к его статическим членам (если таковые имеются)
+
+* Статические конструкторы обычно используются для инициализации статических данных, либо же выполняют действия, которые требуется выполнить только один раз
+
+Определим статический конструктор:
+
+```cs
+class User
+{
+    static User()
+    {
+        Console.WriteLine("Создан первый пользователь");
+    }
+}
+class Program
+{
+    static void Main(string[] args)
+    {
+        User user1 = new User(); // здесь сработает статический конструктор
+        User user2 = new User();
+         
+        Console.Read();
+    }
+}
+```
+
+### Статические классы
+
+Статические классы объявляются с модификатором **static** и могут содержать только статические поля, свойства и методы. Например, если бы класс **Account** имел бы только статические переменные, свойства и методы, то его можно было бы объявить как статический:
+
+```cs
+static class Account
+{
+    private static decimal minSum = 100; // минимальная допустимая сумма для всех счетов
+    public static decimal MinSum
+    {
+        get { return minSum; }
+        set { if(value>0) minSum = value; }
+    }
+ 
+    // подсчет суммы на счете через определенный период по определенной ставке
+    public static decimal GetSum(decimal sum, decimal rate, int period)
+    {
+        decimal result = sum;
+        for (int i = 1; i <= period; i++)
+            result = result + result * rate / 100;
+        return result;
+    }
+}
+```
+
+В C# показательным примером статического класса является класс **Math**, который применяется для различных математических операций.
+
+
+
+
+
+## Наследование
+
+Наследование (inheritance) является одним из ключевых моментов ООП. Благодаря наследованию один класс может унаследовать функциональность другого класса.
+
+Пусть у нас есть следующий класс **Person**, который описывает отдельного человека:
+
+```cs
+class Person
+{
+    private string _name;
+ 
+    public string Name
+    {
+        get { return _name; }
+        set { _name = value; }
+    }
+
+    public void Display()
+    {
+        Console.WriteLine(Name);
+    }
+}
+```
+
+Но вдруг нам потребовался класс, описывающий сотрудника предприятия - класс **Employee**. Поскольку этот класс будет реализовывать тот же функционал, что и класс **Person**, так как сотрудник - это также и человек, то было бы рационально сделать класс **Employee** производным (или наследником, или подклассом) от класса **Person**, который, в свою очередь, называется базовым классом или родителем (или суперклассом):
+
+```cs
+class Employee : Person
+{
+     
+}
+```
+
+После двоеточия мы указываем базовый класс для данного класса. Для класса **Employee** базовым является **Person**, и поэтому класс **Employee** наследует все те же свойства, методы, поля, которые есть в классе Person. Единственное, что не передается при наследовании, это конструкторы базового класса.
+
+Таким образом, наследование реализует отношение **is**-a (является), объект класса **Employee** также является объектом класса **Person**:
+
+```cs
+static void Main(string[] args)
+{
+    Person p = new Person { Name = "Tom"};
+    p.Display();
+    p = new Employee { Name = "Sam" };
+    p.Display();
+    Console.Read();
+}
+```
+
+И поскольку объект **Employee** является также и объектом **Person**, то мы можем так определить переменную: `Person p = new Employee()`.
+
+По умолчанию все классы наследуются от базового класса **Object**, даже если мы явным образом не устанавливаем наследование. Поэтому все классы, кроме своих собственных методов, также будут иметь и методы класса **Object**: *ToString()*, *Equals()*, *GetHashCode()* и *GetType()*.
+
+Все классы по умолчанию могут наследоваться. Однако здесь есть ряд ограничений:
+
+* Не поддерживается множественное наследование, класс может наследоваться только от одного класса.
+
+* При создании производного класса надо учитывать тип доступа к базовому классу - тип доступа к производному классу должен быть таким же, как и у базового класса, или более строгим. То есть, если базовый класс у нас имеет тип доступа **internal**, то производный класс может иметь тип доступа **internal** или **private**, но не **public**.
+
+    Однако следует также учитывать, что если базовый и производный класс находятся в разных сборках (проектах), то в этом случае производый класс может наследовать только от класса, который имеет модификатор **public**.
+
+* Если класс объявлен с модификатором **sealed**, то от этого класса нельзя наследовать и создавать производные классы. Например, следующий класс не допускает создание наследников:
+
+    ```cs
+    sealed class Admin
+    {
+    }
+    ```
+
+* Нельзя унаследовать класс от статического класса.
+
+### Доступ к членам базового класса из класса-наследника
+
+Вернемся к нашим классам **Person** и **Employee**. Хотя **Employee** наследует весь функционал от класса **Person**, посмотрим, что будет в следующем случае:
+
+```cs
+class Employee : Person
+{
+    public void Display()
+    {
+        Console.WriteLine(_name);
+    }
+}
+```
+
+Этот код не сработает и выдаст ошибку, так как переменная *_name* объявлена с модификатором **private** и поэтому к ней доступ имеет только класс **Person**. Но зато в классе **Person** определено общедоступное свойство *Name*, которое мы можем использовать, поэтому следующий код у нас будет работать нормально:
+
+```cs
+class Employee : Person
+{
+    public void Display()
+    {
+        Console.WriteLine(Name);
+    }
+}
+```
+
+Таким образом, производный класс может иметь доступ только к тем членам базового класса, которые определены с модификаторами **private**, **protected** (если базовый и производный класс находятся в одной сборке), **public**, **internal** (если базовый и производный класс находятся в одной сборке), **protected** и **protected internal**.
+
+### Ключевое слово base
+
+Теперь добавим в наши классы конструкторы:
+
+```cs
+class Person
+{
+    public string Name { get;  set; }
+ 
+    public Person(string name)
+    {
+        Name = name;
+    }
+ 
+    public void Display()
+    {
+        Console.WriteLine(Name);
+    }
+}
+ 
+class Employee : Person
+{
+    public string Company { get; set; }
+ 
+    public Employee(string name, string company)
+        : base(name)
+    {
+        Company = company;
+    }
+}
+```
+
+Класс **Person** имеет конструктор, который устанавливает свойство *Name*. Поскольку класс **Employee** наследует и устанавливает то же свойство *Name*, то логично было бы не писать по сто раз код установки, а как-то вызвать соответствующий код класса **Person**. К тому же свойств, которые надо установить в конструкторе базового класса, и параметров может быть гораздо больше.
+
+С помощью ключевого слова **base** мы можем обратиться к базовому классу. В нашем случае в конструкторе класса **Employee** нам надо установить имя и компанию. Но имя мы передаем на установку в конструктор базового класса, то есть в конструктор класса **Person**, с помощью выражения `base(name)`.
+
+```cs
+static void Main(string[] args)
+{
+    Person p = new Person("Bill");
+    p.Display();
+    Employee emp = new Employee ("Tom", "Microsoft");
+    emp.Display();
+    Console.Read();
+}
+```
+
+### Конструкторы в производных классах
+
+Конструкторы не передаются производному классу при наследовании. И если в базовом классе не определен конструктор по умолчанию без параметров, а только конструкторы с параметрами (как в случае с базовым классом **Person**), то в производном классе мы обязательно должны вызвать один из этих конструкторов через ключевое слово **base**. Например, из класса **Employee** уберем определение конструктора:
+
+```cs
+class Employee : Person
+{
+    public string Company { get; set; }
+}
+```
+
+В данном случае мы получим ошибку, так как класс **Employee** не соответствует классу **Person**, а именно не вызывает конструктор базового класса. Даже если бы мы добавили какой-нибудь конструктор, который бы устанавливал все те же свойства, то мы все равно бы получили ошибку:
+
+```cs
+public Employee(string name, string company)
+{
+    Name = name;
+    Company = company;
+}
+```
+
+То есть в классе **Employee** через ключевое слово **base** надо явным образом вызвать конструктор класса **Person**:
+
+```cs
+public Employee(string name, string company)
+        : base(name)
+{
+    Company = company;
+}
+```
+
+Либо в качестве альтернативы мы могли бы определить в базовом классе конструктор без параметров:
+
+```cs
+class Person
+{
+    // остальной код класса
+    // конструктор по умолчанию
+    public Person()
+    {
+        FirstName = "Tom";
+        Console.WriteLine("Вызов конструктора без параметров");
+    }
+}
+```
+
+Тогда в любом конструкторе производного класса, где нет обращения к конструктору базового класса, все равно неявно вызывался бы этот конструктор по умолчанию. Например, следующий конструктор
+
+```cs
+public Employee(string company)
+{
+    Company = company;
+}
+```
+
+Фактически был бы эквивалентен следующему конструктору:
+
+```cs
+public Employee(string company)
+    :base()
+{
+    Company = company;
+}
+```
+
+### Порядок вызова конструкторов
+
+При вызове конструктора класса сначала отрабатывают конструкторы базовых классов и только затем конструкторы производных. Например, возьмем следующие классы:
+
+```cs
+class Person
+{
+    string name;
+    int age;
+ 
+    public Person(string name)
+    {
+        this.name = name;
+        Console.WriteLine("Person(string name)");
+    }
+    public Person(string name, int age) : this(name)
+    {
+        this.age = age;
+        Console.WriteLine("Person(string name, int age)");
+    }
+}
+class Employee : Person
+{
+    string company;
+ 
+    public Employee(string name, int age, string company) : base(name, age)
+    {
+        this.company = company;
+        Console.WriteLine("Employee(string name, int age, string company)");
+    }
+}
+```
+
+При создании объекта Employee:
+
+```cs
+Employee tom = new Employee("Tom", 22, "Microsoft");
+```
+
+Мы получим следующий консольный вывод:
+
+```
+Person(string name)
+Person(string name, int age)
+Employee(string name, int age, string company)
+```
+
+В итоге мы получаем следующую цепь выполнений.
+
+* Вначале вызывается конструктор `Employee(string name, int age, string company)`. Он делегирует выполнение конструктору `Person(string name, int age)`
+
+* Вызывается конструктор `Person(string name, int age)`, который сам пока не выполняется и передает выполнение конструктору `Person(string name)`
+
+* Вызывается конструктор `Person(string name)`, который передает выполнение конструктору класса **System.Object**, так как это базовый по умолчанию класс для **Person**.
+
+* Выполняется конструктор *System.Object.Object()*, затем выполнение возвращается конструктору `Person(string name)`
+
+* Выполняется тело конструктора `Person(string name)`, затем выполнение возвращается конструктору `Person(string name, int age)`
+
+* Выполняется тело конструктора `Person(string name, int age)`, затем выполнение возвращается конструктору `Employee(string name, int age, string company)`
+
+* Выполняется тело конструктора `Employee(string name, int age, string company)`. В итоге создается объект Employee
+
+## Виртуальные методы и свойства
+
+При наследовании нередко возникает необходимость изменить в классе-наследнике функционал метода, который был унаследован от базового класса. В этом случае класс-наследник может переопределять методы и свойства базового класса.
+
+Те методы и свойства, которые мы хотим сделать доступными для переопределения, в базовом классе помечается модификатором **virtual**. Такие методы и свойства называют виртуальными.
+
+А чтобы переопределить метод в классе-наследнике, этот метод определяется с модификатором **override**. Переопределенный метод в классе-наследнике должен иметь тот же набор параметров, что и виртуальный метод в базовом классе.
+
+Например, рассмотрим следующие классы:
+
+```cs
+class Person
+{
+    public string Name { get; set; }
+    public Person(string name)
+    {
+        Name = name;
+    }
+    public virtual void Display()
+    {
+        Console.WriteLine(Name);
+    }
+}
+class Employee : Person
+{
+    public string Company { get; set; }
+    public Employee(string name, string company) : base(name)
+    {
+        Company = company;
+    }
+}
+```
+
+Здесь класс **Person** представляет человека. Класс **Employee** наследуется от **Person** и представляет сотруднника предприятия. Этот класс кроме унаследованного свойства *Name* имеет еще одно свойство - *Company*.
+
+Чтобы сделать метод *Display* доступным для переопределения, этот метод определен с модификатором **virtual**. Поэтому мы можем переопределить этот метод, но можем и не переопределять. Допустим, нас устраивает реализация метода из базового класса. В этом случае объекты **Employee** будут использовать реализацию метода *Display* из класса *Person*:
+
+```cs
+static void Main(string[] args)
+{
+    Person p1 = new Person("Bill");
+    p1.Display(); // вызов метода Display из класса Person
+ 
+    Employee p2 = new Employee("Tom", "Microsoft");
+    p2.Display(); // вызов метода Display из класса Person
+ 
+    Console.ReadKey();
+}
+```
+
+Консольный вывод:
+
+```
+Bill
+Tom
+```
+
+Но также можем переопределить виртуальный метод. Для этого в классе-наследнике определяется метод с модификатором **override**, который имеет то же самое имя и набор параметров:
+
+```cs
+class Employee : Person
+{
+    public string Company { get; set; }
+    public Employee(string name, string company)
+        : base(name)
+    {
+        Company = company;
+    }
+ 
+    public override void Display()
+    {
+        Console.WriteLine($"{Name} работает в {Company}");
+    }
+}
+```
+
+Возьмем те же самые объекты:
+
+```cs
+static void Main(string[] args)
+{
+    Person p1 = new Person("Bill");
+    p1.Display(); // вызов метода Display из класса Person
+ 
+    Employee p2 = new Employee("Tom", "Microsoft");
+    p2.Display(); // вызов метода Display из класса Employee
+ 
+    Console.ReadKey();
+}
+```
+
+Консольный вывод:
+
+```
+Bill
+Tom работает в Microsoft
+```
+
+Виртуальные методы базового класса определяют интерфейс всей иерархии, то есть в любом производном классе, который не является прямым наследником от базового класса, можно переопределить виртуальные методы. Например, мы можем определить класс **Manager**, который будет производным от **Employee**, и в нем также переопределить метод **Display**.
+
+При переопределении виртуальных методов следует учитывать ряд ограничений:
+
+* Виртуальный и переопределенный методы должны иметь один и тот же модификатор доступа. То есть если виртуальный метод определен с помощью модификатора **public**, то и переопредленный метод также должен иметь модификатор **public**.
+
+* Нельзя переопределить или объявить виртуальным статический метод.
+
+### Переопределение свойств
+
+Также как и методы, можно переопределять свойства:
+
+```cs
+class Credit
+{
+    public virtual decimal Sum { get; set; }
+}
+class LongCredit : Credit
+{
+    private decimal sum;
+    public override decimal Sum
+    {
+        get
+        {
+            return sum;
+        }
+        set
+        {
+            if(value > 1000)
+            {
+                sum = value;
+            }
+        }
+    }
+}
+class Program
+{
+    static void Main(string[] args)
+    {
+        LongCredit credit = new LongCredit { Sum = 6000 };
+        credit.Sum = 490;
+        Console.WriteLine(credit.Sum);
+        Console.ReadKey();
+    }
+}
+```
+
+### Ключевое слово **base**
+
+Кроме конструкторов, мы можем обратиться с помощью ключевого слова **base** к другим членам базового класса. В нашем случае вызов `base.Display();` будет обращением к методу *Display()* в классе **Person**:
+
+```cs
+class Employee : Person
+{
+    public string Company { get; set; }
+  
+    public Employee(string name, string company)
+            :base(name)
+    {
+        Company = company;
+    }
+  
+    public override void Display()
+    {
+        base.Display();
+        Console.WriteLine($"работает в {Company}");
+    }
+}
+```
+
+### Запрет переопределения методов
+
+Также можно запретить переопределение методов и свойств. В этом случае их надо объявлять с модификатором **sealed**:
+
+```cs
+class Employee : Person
+{
+    public string Company { get; set; }
+  
+    public Employee(string name, string company)
+                : base(name)
+    {
+        Company = company;
+    }
+ 
+    public override sealed void Display()
+    {
+        Console.WriteLine($"{Name} работает в {Company}");
+    }
+}
+```
+
+При создании методов с модификатором **sealed** надо учитывать, что **sealed** применяется в паре с **override**, то есть только в переопределяемых методах.
+
+И в этом случае мы не сможем переопределить метод *Display* в классе, унаследованном от **Employee**.
+
+## Абстрактные классы и члены классов
+
+Кроме обычных классов в C# есть абстрактные классы. Абстрактный класс похож на обычный класс. Он также может иметь переменные, методы, конструкторы, свойства. Единственное, что при определении абстрактных классов используется ключевое слово **abstract**:
+
+```cs
+abstract class Human
+{
+    public int Length { get; set; }
+    public double Weight { get; set; }
+}
+```
+
+Но главное отличие состоит в том, что мы не можем использовать конструктор абстрактного класса для создания его объекта. Например, следующим образом:
+
+```cs
+Human h = new Human();
+```
+
+Зачем нужны абстрактные классы? Допустим, в нашей программе для банковского сектора мы можем определить две основных сущности: клиента банка и сотрудника банка. Каждая из этих сущностей будет отличаться, например, для сотрудника надо определить его должность, а для клиента - сумму на счете. Соответственно клиент и сотрудник будут составлять отдельные классы Client и Employee. В то же время обе этих сущности могут иметь что-то общее, например, имя и фамилию, какую-то другую общую функциональность. И эту общую функциональность лучше вынести в какой-то отдельный класс, например, Person, который описывает человека. То есть классы Employee (сотрудник) и Client (клиент банка) будут производными от класса Person. И так как все объекты в нашей системе будут представлять либо сотрудника банка, либо клиента, то напрямую мы от класса Person создавать объекты не будем. Поэтому имеет смысл сделать его абстрактным:
+
+```cs
+abstract class Person
+{
+    public string Name { get; set; }
+ 
+    public Person(string name)
+    {
+        Name = name;
+    }
+ 
+    public void Display()
+    {
+        Console.WriteLine(Name);
+    }
+}
+ 
+class Client : Person
+{
+    public int Sum { get; set; }    // сумма на счету
+ 
+    public Client(string name, int sum)
+        : base(name)
+    {
+        Sum = sum;
+    }
+}
+ 
+class Employee : Person
+{
+    public string Position { get; set; } // должность
+ 
+    public Employee(string name, string position) 
+        : base(name)
+    {
+            Position = position;
+    }
+}
+```
+
+Затем мы сможем использовать эти классы:
+
+```cs
+Client client = new Client("Tom", 500);
+Employee employee = new Employee ("Bob", "Apple");
+client.Display();
+employee.Display();
+```
+
+Или даже так:
+
+```cs
+Person client = new Client("Tom", 500);
+Person employee = new Employee ("Bob", "Операционист");
+```
+
+Но мы НЕ можем создать объект **Person**, используя конструктор класса **Person**:
+
+```cs
+Person person = new Person ("Bill");
+```
+
+Однако несмотря на то, что напрямую мы не можем вызвать конструктор класса **Person** для создания объекта, тем не менее конструктор в абстрактных классах то же может играть важную роль, в частности, инициализировать некоторые общие для производных классов переменные и свойства, как в случае со свойством Name. И хотя в примере выше конструктор класса Person не вызывается, тем не менее производные классы Client и Employee могут обращаться к нему.
+
+### Абстрактные члены классов
+
+Кроме обычных свойств и методов абстрактный класс может иметь абстрактные члены классов, которые определяются с помощью ключевого слова **abstract** и не имеют никакого функционала. В частности, абстрактными могут быть:
+
+* Методы
+
+* Свойства
+
+* Индексаторы
+
+* События
+
+Абстрактные члены классов не должны иметь модификатор **private**. При этом производный класс обязан переопределить и реализовать все абстрактные методы и свойства, которые имеются в базовом абстрактном классе. При переопределении в производном классе такой метод или свойство также объявляются с модификатором **override** (как и при обычном переопределении виртуальных методов и свойств). Также следует учесть, что если класс имеет хотя бы одный абстрактный метод (или абстрактные свойство, индексатор, событие), то этот класс должен быть определен как абстрактный.
+
+Абстрактные члены также, как и виртуальные, являются частью полиморфного интерфейса. Но если в случае с виртуальными методами мы говорим, что класс-наследник наследует реализацию, то в случае с абстрактными методами наследуется интерфейс, представленный этими абстрактными методами.
+
+### Абстрактные методы
+
+Например, сделаем в примере выше метод *Display* абстрактным:
+
+```cs
+abstract class Person
+{
+    public string Name { get; set; }
+ 
+    public Person(string name)
+    {
+        Name = name;
+    }
+ 
+    public abstract void Display();
+}
+ 
+class Client : Person
+{
+    public int Sum { get; set; }    // сумма на счету
+ 
+    public Client(string name, int sum)
+        : base(name)
+    {
+        Sum = sum;
+    }
+    public override void Display()
+    {
+        Console.WriteLine($"{Name} имеет счет на сумму {Sum}");
+    }
+}
+ 
+class Employee : Person
+{
+    public string Position { get; set; } // должность
+ 
+    public Employee(string name, string position) 
+        : base(name)
+    {
+        Position = position;
+    }
+ 
+    public override void Display()
+    {
+        Console.WriteLine($"{Position} {Name}");
+    }
+}
+```
+
+### Абстрактные свойства
+
+Следует отметить использование абстрактных свойств. Их определение похоже на определение автосвойств. Например:
+
+```cs
+abstract class Person
+{
+    public abstract string Name { get; set; }
+}
+ 
+class Client : Person
+{
+    private string name;
+ 
+    public override string Name
+    {
+        get { return "Mr/Ms. " + name; }
+        set { name = value; }
+    }
+}
+ 
+class Employee : Person
+{
+    public override string Name { get; set; }
+}
+```
+
+В классе Person определено абстрактное свойство Name. Оно похоже на автосвойство, но это не автосвойство. Так как данное свойство не должно иметь реализацию, то оно имеет только пустые блоки get и set. В производных классах мы можем переопределить это свойство, сделав его полноценным свойством (как в классе Client), либо же сделав его автоматическим (как в классе Employee).
+
+### Отказ от реализации абстрактных членов
+
+Производный класс обязан реализовать все абстрактные члены базового класса. Однако мы можем отказаться от реализации, но в этом случае производный класс также должен быть определен как абстрактный:
+
+```cs
+abstract class Person
+{
+    public abstract string Name { get; set; }
+}
+ 
+abstract class Manager : Person
+{
+}
+```
+
+### Пример абстрактного класса
+
+Xрестоматийным примером является система геометрических фигур. В реальности не существует геометрической фигуры как таковой. Есть круг, прямоугольник, квадрат, но просто фигуры нет. Однако же и круг, и прямоугольник имеют что-то общее и являются фигурами:
+
+```cs
+// абстрактный класс фигуры
+abstract class Figure
+{
+    // абстрактный метод для получения периметра
+    public abstract float Perimeter();
+    // абстрактный метод для получения площади
+    public abstract float Area();
+}
+// производный класс прямоугольника
+class Rectangle : Figure
+{
+    public float Width { get; set; }
+    public float Height { get; set; }
+ 
+    public Rectangle(float width, float height)
+    {
+        this.Width = width;
+        this.Height = height;
+    }
+    // переопределение получения периметра
+    public override float Perimeter()
+    {
+        return Width * 2 + Height * 2;
+    }
+    // переопрелеление получения площади
+    public override float Area()
+    {
+        return Width * Height;
+    }
+}
+```
+
+---
+
+## Контрольные вопросы
+
+1. Члены класса
+2. Константы класса
+3. Конструкторы класса
+4. Что такое this
+5. Инициализатор объекта 
+6. Модификаторы доступа
+7. Свойства класса
+8. Перегрузка методов
+
+## Задание на дом
+
+Написать иерархию классов для произвольной предметной области. Предметная область должна быть уникальной. Выбранную предметную область вы будете использовать до конца курса и, если захотите, продолжите использовать на следующих курсах при написании курсовых проектов.
+
+Например, предметная область "Ресторан". В ресторане есть меню (с категориями) и сотрудники (с ролями):
+
+```cs
+class Category {
+    public string title { get;set; }
+}
+
+class Menu {
+    public string title { get;set; }
+    public string description { get;set; }
+    public Category category { get;set; }
+}
+
+class Role {
+    public string title { get;set; }
+}
+
+class User {
+    public Role role { get;set; }
+    public string firstName { get;set; }
+    public string lastName { get;set; }
+}
+
+var menuItem = new Menu {
+    title = "Бургер",
+    description = "Описание бургера",
+    category = new Category { title = "фастфуд" }
+}
+```
+
+Приветствуется использование конструкторов и наследования
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Регулярные выражения](./t5_regex.md) | [Содержание](../readme.md#тема-6-основные-принципы-объектно-ориентированного-программирования) | [Ещё раз про классы. Интерфейсы.](./t6_oop_habr.md)

+ 687 - 0
articles/t6_oop2.md

@@ -0,0 +1,687 @@
+* Перегрузка операторов
+* Индексаторы
+
+## Перегрузка операторов
+
+Наряду с методами мы можем также перегружать операторы. Например, пусть у нас есть следующий класс Counter:
+
+1
+2
+3
+4
+class Counter
+{
+    public int Value { get; set; }
+}
+Данный класс представляет некоторый счетчик, значение которого хранится в свойстве Value.
+
+И допустим, у нас есть два объекта класса Counter - два счетчика, которые мы хотим сравнивать или складывать на основании их свойства Value, используя стандартные операции сравнения и сложения:
+
+1
+2
+3
+4
+5
+Counter c1 = new Counter { Value = 23 };
+Counter c2 = new Counter { Value = 45 };
+ 
+bool result = c1 > c2;
+Counter c3 = c1 + c2;
+Но на данный момент ни операция сравнения, ни операция сложения для объектов Counter не доступны. Эти операции могут использоваться для ряда примитивных типов. Например, по умолчанию мы можем складывать числовые значения, но как складывать объекты комплексных типов - классов и структур компилятор не знает. И для этого нам надо выполнить перегрузку нужных нам операторов.
+
+Перегрузка операторов заключается в определении в классе, для объектов которого мы хотим определить оператор, специального метода:
+
+1
+2
+public static возвращаемый_тип operator оператор(параметры)
+{  }
+Этот метод должен иметь модификаторы public static, так как перегружаемый оператор будет использоваться для всех объектов данного класса. Далее идет название возвращаемого типа. Возвращаемый тип представляет тот тип, объекты которого мы хотим получить. К примеру, в результате сложения двух объектов Counter мы ожидаем получить новый объект Counter. А в результате сравнения двух мы хотим получить объект типа bool, который указывает истинно ли условное выражение или ложно. Но в зависимости от задачи возвращаемые типы могут быть любыми.
+
+Затем вместо названия метода идет ключевое слово operator и собственно сам оператор. И далее в скобках перечисляются параметры. Бинарные операторы принимают два параметра, унарные - один параметр. И в любом случае один из параметров должен представлять тот тип - класс или структуру, в котором определяется оператор.
+
+Например, перегрузим ряд операторов для класса Counter:
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+class Counter
+{
+    public int Value { get; set; }
+         
+    public static Counter operator +(Counter c1, Counter c2)
+    {
+        return new Counter { Value = c1.Value + c2.Value };
+    }
+    public static bool operator >(Counter c1, Counter c2)
+    {
+        return c1.Value > c2.Value;
+    }
+    public static bool operator <(Counter c1, Counter c2)
+    {
+        return c1.Value < c2.Value;
+    }
+}
+Поскольку все перегруженные операторы - бинарные - то есть проводятся над двумя объектами, то для каждой перегрузки предусмотрено по два параметра.
+
+Так как в случае с операцией сложения мы хотим сложить два объекта класса Counter, то оператор принимает два объекта этого класса. И так как мы хотим в результате сложения получить новый объект Counter, то данный класс также используется в качестве возвращаемого типа. Все действия этого оператора сводятся к созданию, нового объекта, свойство Value которого объединяет значения свойства Value обоих параметров:
+
+1
+2
+3
+4
+public static Counter operator +(Counter c1, Counter c2)
+{
+    return new Counter { Value = c1.Value + c2.Value };
+}
+Также переопределены две операции сравнения. Если мы переопределяем одну из этих операций сравнения, то мы также должны переопределить вторую из этих операций. Сами операторы сравнения сравнивают значения свойств Value и в зависимости от результата сравнения возвращают либо true, либо false.
+
+Теперь используем перегруженные операторы в программе:
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+static void Main(string[] args)
+{
+    Counter c1 = new Counter { Value = 23 };
+    Counter c2 = new Counter { Value = 45 };
+    bool result = c1 > c2;
+    Console.WriteLine(result); // false
+ 
+    Counter c3 = c1 + c2;
+    Console.WriteLine(c3.Value);  // 23 + 45 = 68
+     
+    Console.ReadKey();
+}
+Стоит отметить, что так как по сути определение оператора представляет собой метод, то этот метод мы также можем перегрузить, то есть создать для него еще одну версию. Например, добавим в класс Counter еще один оператор:
+
+1
+2
+3
+4
+public static int operator +(Counter c1, int val)
+{
+    return c1.Value + val;
+}
+Данный метод складывает значение свойства Value и некоторое число, возвращая их сумму. И также мы можем применить этот оператор:
+
+1
+2
+3
+Counter c1 = new Counter { Value = 23 };
+int d = c1 + 27; // 50
+Console.WriteLine(d);
+Следует учитывать, что при перегрузке не должны изменяться те объекты, которые передаются в оператор через параметры. Например, мы можем определить для класса Counter оператор инкремента:
+
+1
+2
+3
+4
+5
+public static Counter operator ++(Counter c1)
+{
+    c1.Value += 10;
+    return c1;
+}
+Поскольку оператор унарный, он принимает только один параметр - объект того класса, в котором данный оператор определен. Но это неправильное определение инкремента, так как оператор не должен менять значения своих параметров.
+
+И более корректная перегрузка оператора инкремента будет выглядеть так:
+
+1
+2
+3
+4
+public static Counter operator ++(Counter c1)
+{
+    return new Counter { Value = c1.Value + 10 };
+}
+То есть возвращается новый объект, который содержит в свойстве Value инкрементированное значение.
+
+При этом нам не надо определять отдельно операторы для префиксного и для постфиксного инкремента (а также декремента), так как одна реализация будет работать в обоих случаях.
+
+Например, используем операцию префиксного инкремента:
+
+1
+2
+3
+4
+Counter counter = new Counter() { Value = 10 };
+Console.WriteLine($"{counter.Value}");      // 10
+Console.WriteLine($"{(++counter).Value}");  // 20
+Console.WriteLine($"{counter.Value}");      // 20
+Консольный вывод:
+
+10
+20
+20
+Теперь используем постфиксный инкремент:
+
+1
+2
+3
+4
+Counter counter = new Counter() { Value = 10 };
+Console.WriteLine($"{counter.Value}");      // 10
+Console.WriteLine($"{(counter++).Value}");  // 10
+Console.WriteLine($"{counter.Value}");      // 20
+Консольный вывод:
+
+10
+10
+20
+Также стоит отметить, что мы можем переопределить операторы true и false. Например, определим их в классе Counter:
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+class Counter
+{
+    public int Value { get; set; }
+     
+    public static bool operator true(Counter c1)
+    {
+        return c1.Value != 0;
+    }
+    public static bool operator false(Counter c1)
+    {
+        return c1.Value == 0;
+    }
+     
+    // остальное содержимое класса
+}
+Эти операторы перегружаются, когда мы хотим использовать объект типа в качестве условия. Например:
+
+1
+2
+3
+4
+5
+Counter counter = new Counter() { Value = 0 };
+if (counter)
+    Console.WriteLine(true);
+else
+    Console.WriteLine(false);
+При перегрузке операторов надо учитывать, что не все операторы можно перегрузить. В частности, мы можем перегрузить следующие операторы:
+
+унарные операторы +, -, !, ~, ++, --
+
+бинарные операторы +, -, *, /, %
+
+операции сравнения ==, !=, <, >, <=, >=
+
+логические операторы &&, ||
+
+И есть ряд операторов, которые нельзя перегрузить, например, операцию равенства = или тернарный оператор ?:, а также ряд других.
+
+Полный список перегружаемых операторов можно найти в документации msdn
+
+При перегрузке операторов также следует помнить, что мы не можем изменить приоритет оператора или его ассоциативность, мы не можем создать новый оператор или изменить логику операторов в типах, который есть по умолчанию в .NET.
+
+## Индексаторы
+
+Индексаторы позволяют индексировать объекты и обращаться к данным по индексу. Фактически с помощью индексаторов мы можем работать с объектами как с массивами. По форме они напоминают свойства со стандартными блоками get и set, которые возвращают и присваивают значение.
+
+Формальное определение индексатора:
+
+1
+2
+3
+4
+5
+возвращаемый_тип this [Тип параметр1, ...]
+{
+    get { ... }
+    set { ... }
+}
+В отличие от свойств индексатор не имеет названия. Вместо него указывается ключевое слово this, после которого в квадратных скобках идут параметры. Индексатор должен иметь как минимум один параметр.
+
+Посмотрим на примере. Допустим, у нас есть класс Person, который представляет человека, и класс People, который представляет группу людей. Используем индексаторы для определения класса People:
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+class Person
+{
+    public string Name { get; set; }
+}
+class People
+{
+    Person[] data;
+    public People()
+    {
+        data = new Person[5];
+    }
+    // индексатор
+    public Person this[int index]
+    {
+        get
+        {
+            return data[index];
+        }
+        set
+        {
+            data[index] = value;
+        }
+    }
+}
+Конструкция public Person this[int index] и представляет индексатор. Здесь определяем, во-первых, тип возвращаемого или присваиваемого объекта, то есть тип Person. Во-вторых, определяем через параметр int index способ доступа к элементам.
+
+По сути все объекты Person хранятся в классе в массиве data. Для получения их по индексу в индексаторе определен блок get:
+
+1
+2
+3
+4
+get
+{
+    return data[index];
+}
+Поскольку индексатор имеет тип Person, то в блоке get нам надо возвратить объект этого типа с помощью оператора return. Здесь мы можем определить разнообразную логику. В данном случае просто возвращаем объект из массива data.
+
+В блоке set получаем через параметр value переданный объект Person и сохраняем его в массив по индексу.
+
+1
+2
+3
+4
+set
+{
+    data[index] = value;
+}
+После этого мы можем работать с объектом People как с набором объектов Person:
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+class Program
+    {
+    static void Main(string[] args)
+    {
+        People people = new People();
+        people[0] = new Person { Name = "Tom" };
+        people[1] = new Person { Name = "Bob" };
+ 
+        Person tom = people[0];
+        Console.WriteLine(tom?.Name);
+ 
+        Console.ReadKey();
+    }
+}
+Индексатор, как полагается получает набор индексов в виде параметров. Однако индексы необязательно должны представлять тип int. Например, мы можем рассматривать объект как хранилище свойств и передавать имя атрибута объекта в виде строки:
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+class User
+{
+    string name;
+    string email;
+    string phone;
+ 
+    public string this[string propname]
+    {
+        get
+        {
+            switch (propname)
+            {
+                case "name": return "Mr/Ms. " + name;
+                case "email": return email;
+                case "phone": return phone;
+                default: return null;
+            }
+        }
+        set
+        {
+            switch (propname)
+            {
+                case "name":
+                    name = value;
+                    break;
+                case "email":
+                    email = value;
+                    break;
+                case "phone":
+                    phone = value;
+                    break;
+            }
+        }
+    }
+}
+class Program
+{
+    static void Main(string[] args)
+    {
+        User tom = new User();
+        tom["name"] = "Tom";
+        tom["email"] = "tomekvilmovskiy@gmail.ru";
+ 
+        Console.WriteLine(tom["name"]); // Mr/Ms. Tom
+ 
+        Console.ReadKey();
+    }
+}
+Применение нескольких параметров
+Также индексатор может принимать несколько параметров. Допустим, у нас есть класс, в котором хранилище определено в виде двухмерного массива или матрицы:
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+class Matrix
+{
+    private int[,] numbers = new int[,] { { 1, 2, 4}, { 2, 3, 6 }, { 3, 4, 8 } };
+    public int this[int i, int j]
+    {
+        get
+        {
+            return numbers[i,j];
+        }
+        set
+        {
+            numbers[i, j] = value;
+        }
+    }
+}
+Теперь для определения индексатора используются два индекса - i и j. И в программе мы уже должны обращаться к объекту, используя два индекса:
+
+1
+2
+3
+4
+Matrix matrix = new Matrix();
+Console.WriteLine(matrix[0, 0]);
+matrix[0, 0] = 111;
+Console.WriteLine(matrix[0, 0]);
+Следует учитывать, что индексатор не может быть статическим и применяется только к экземпляру класса. Но при этом индексаторы могут быть виртуальными и абстрактными и могут переопределяться в произодных классах.
+
+Блоки get и set
+Как и в свойствах, в индексаторах можно опускать блок get или set, если в них нет необходимости. Например, удалим блок set и сделаем индексатор доступным только для чтения:
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+class Matrix
+{
+    private int[,] numbers = new int[,] { { 1, 2, 4}, { 2, 3, 6 }, { 3, 4, 8 } };
+    public int this[int i, int j]
+    {
+        get
+        {
+            return numbers[i,j];
+        }
+    }
+}
+Также мы можем ограничивать доступ к блокам get и set, используя модификаторы доступа. Например, сделаем блок set приватным:
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+class Matrix
+{
+    private int[,] numbers = new int[,] { { 1, 2, 4}, { 2, 3, 6 }, { 3, 4, 8 } };
+    public int this[int i, int j]
+    {
+        get
+        {
+            return numbers[i,j];
+        }
+        private set
+        {
+            numbers[i, j] = value;
+        }
+    }
+}
+Перегрузка индексаторов
+Подобно методам индексаторы можно перегружать. В этом случае также индексаторы должны отличаться по количеству, типу или порядку используемых параметров. Например:
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+class Person
+{
+    public string Name { get; set; }
+    public int Age { get; set; }
+}
+class People
+{
+    Person[] data;
+    public People()
+    {
+        data = new Person[5];
+    }
+    public Person this[int index]
+    {
+        get
+        {
+            return data[index];
+        }
+        set
+        {
+            data[index] = value;
+        }
+    }
+    public Person this[string name]
+    {
+        get
+        {
+            Person person = null;
+            foreach(var p in data)
+            {
+                if(p?.Name == name)
+                {
+                    person = p;
+                    break;
+                }
+            }
+            return person;
+        }
+    }
+}
+class Program
+{
+    static void Main(string[] args)
+    {
+        People people = new People();
+        people[0] = new Person { Name = "Tom" };
+        people[1] = new Person { Name = "Bob" };
+             
+        Console.WriteLine(people[0].Name);      // Tom
+        Console.WriteLine(people["Bob"].Name);  // Bob
+ 
+        Console.ReadKey();
+    }
+}
+В данном случае класс People содержит две версии индексатора. Первая версия получает и устанавливает объект Person по индексу, а вторая - только получае объект Person по его имени.

+ 359 - 0
articles/t6_oop_habr.md

@@ -0,0 +1,359 @@
+<table style="width: 100%;"><tr><td style="width: 40%;">
+<a href="../articles/t6_oop1.md">История развития ООП. Базовые понятия: объект, его свойства и методы, класс, интерфейс. Основные принципы ООП: инкапсуляция, наследование, полиморфизм.
+</a></td><td style="width: 20%;">
+<a href="../readme.md">Содержание
+</a></td><td style="width: 40%;">
+<a href="../articles/t6_linq.md">LINQ.
+</a></td><tr></table>
+
+## Базовые понятия: объект, его свойства и методы, класс, интерфейс. 
+
+В силу своей природы, объектно-ориентированное программирование лучше всего объяснять на примерах. [Нашими пациентами будут трансформеры.](https://habr.com/ru/post/463125/)
+
+### Класс и объект
+
+Самое простое объяснение: **класс** — это чертеж трансформера, а экземпляры этого класса — конкретные трансформеры, например, *Оптимус Прайм* или *Олег*. И хотя они и собраны по одному чертежу, умеют одинаково ходить, трансформироваться и стрелять, они оба обладают собственным уникальным состоянием. Состояние — это ряд меняющихся свойств. Поэтому у двух разных объектов одного **класса** мы можем наблюдать разное имя, возраст, местоположение, уровень заряда, количество боеприпасов и т. д. Само наличие этих свойств и их типы описываются в классе.
+
+Таким образом, **класс** — это описание того, какими свойствами и поведением будет обладать объект. А **объект** — это экземпляр с собственным состоянием этих свойств.
+
+Мы говорим «свойства и поведение», но звучит это как-то абстрактно и непонятно. Привычнее для программиста будет звучать так: «переменные и функции». На самом деле «свойства» — это такие же обычные переменные, просто они являются атрибутами какого-то объекта (их называют полями объекта). Аналогично «поведение» — это функции объекта (их называют методами), которые тоже являются атрибутами объекта. Разница между методом объекта и обычной функцией лишь в том, что метод имеет доступ к собственному состоянию через поля.
+
+Итого, имеем методы и свойства, которые являются атрибутами. Как работать с атрибутами? В большинстве ЯП оператор обращения к атрибуту — это точка. Выглядит это примерно вот так:
+
+```cs
+// объявление класса с помощью ключевого слова class
+class Transformer
+{
+    public int x
+
+    // объявление метода run
+    public void run(){
+        // обращение к собственному атрибуту 
+        x += 1;
+    }
+
+    public Transformer(int x){
+        this.x = x;
+    }
+}
+
+// а теперь клиентский код:
+
+// создаем новый экземпляр трансформера с начальной позицией 0
+var optimus = new Transformer(0);
+
+// приказываем Оптимусу бежать
+optimus.run();
+
+// выведет 1
+Console.WriteLine(optimus.x);
+
+// приказывает Оптимусу еще раз бежать
+optimus.run();
+
+// выведет 2
+Console.WriteLine(optimus.x);
+```
+
+Что мы видим из кода?
+
+1. Объект может обращаться из своих методов к собственным атрибутам (у нас атрибут "**x**"). Обращаю внимание, что только к собственным, то бишь, когда трансформер вызывает свой метод, либо меняет собственное состояние. Если снаружи обращение будет выглядеть так: *optimus.x*, то изнутри, если Оптимус захочет сам обратиться к своему полю "x", в его методе обращение будет звучать так: "x" (или *this.x*), то есть "я (Оптимус) обращаюсь к своему атрибуту x". В большинстве языков для обращения к аттрибутам класса используются ключевые слова this или self.
+
+2. Конструктор — это специальный метод, который автоматически вызывается при создании объекта. Конструктор может принимать любые аргументы, как и любой другой метод. В каждом языке конструктор обозначается своим именем. Где-то это специально зарезервированные имена типа ``__construct`` или ``__init__``, а где-то имя конструктора должно совпадать с именем класса (как раз в C# так и сделано). Назначение конструкторов — произвести первоначальную инициализацию объекта, заполнить нужные поля. Про конструкторы мы подробнее поговорим ниже.
+
+3. В C# для создания экземпляра класса нужно вызвать конструктор класса (как функцию) с ключевым словом **new**. В этот момент создается объект и вызывается конструктор. В нашем примере, конструктору передается "0" в качестве стартовой позиции трансформера (это и есть вышеупомянутая инициализация).
+
+4. Методы *Transformer* (конструктор) и *run* работают с внутренним состоянием, а во всем остальном не отличаются от обычных функций. Даже синтаксис объявления совпадает.
+
+### Интерфейс
+
+Когда мы подходим к автомату с кофе или садимся за руль, мы начинаем взаимодействие с ними. Обычно, взаимодействие происходит с помощью некоторого набора элементов: щель для приёмки монеток, кнопка выбора напитка и отсек выдачи стакана в кофейном автомате; руль, педали, рычаг коробки переключения передач в автомобиле. Всегда существует некоторый ограниченный набор элементов управления, с которыми мы можем взаимодействовать.
+
+**Интерфейс** – это набор методов класса, доступных для использования другими классами. 
+
+Очевидно, что интерфейсом класса будет являться набор всех его публичных методов в совокупности с набором публичных атрибутов. По сути, интерфейс специфицирует класс, чётко определяя все возможные действия над ним. 
+
+Хорошим примером интерфейса может служить приборная панель автомобиля, которая позволяет вызвать такие методы, как увеличение скорости, торможение, поворот, переключение передач, включение фар, и т.п. То есть все действия, которые может осуществить другой класс (в нашем случае – водитель) при взаимодействии с автомобилем.
+
+При описании интерфейса класса очень важно соблюсти баланс между гибкостью и простотой. Класс с простым интерфейсом будет легко использовать, но будут существовать задачи, которые с помощью него решить будет не под силу. В то же время, если интерфейс будет гибким, то, скорее всего, он будет состоять из достаточно сложных методов с большим количеством параметров, которые будут позволять делать очень многое, но использование его будет сопряжено с большими сложностями и риском совершить ошибку, что-то перепутав.
+
+Примером простого интерфейса может служить машина с коробкой-автоматом. Освоить её управление очень быстро сможет любая блондинка, окончившая двухнедельные курсы вождения. С другой стороны, чтобы освоить управление современным пассажирским самолётом, необходимо несколько месяцев, а то и лет упорных тренировок.
+
+## Основные принципы ООП.
+
+Ортодоксальная ООП-церковь проповедует нам фундаментальную троицу — *инкапсуляцию*, *полиморфизм* и *наследование*, на которых зиждется весь объектно-ориентированный подход. 
+
+Разберем их по порядку.
+
+### Наследование
+
+**Наследование** — это механизм системы, который позволяет, как бы парадоксально это не звучало, наследовать одними классами свойства и поведение других классов для дальнейшего расширения или модификации.
+
+Что если, мы не хотим штамповать одинаковых трансформеров, а хотим сделать общий каркас, но с разным обвесом? ООП позволяет нам такую шалость путем разделения логики на сходства и различия с последующим выносом сходств в родительский класс, а различий в классы-потомки. Как это выглядит?
+
+*Оптимус Прайм* и *Мегатрон* — оба трансформеры, но один является автоботом, а второй десептиконом. Допустим, что различия между автоботами и десептиконами будут заключаться только в том, что автоботы трансформируются в автомобили, а десептиконы — в авиацию. Все остальные свойства и поведение не будут иметь никакой разницы. В таком случае можно спроектировать систему наследования так: общие черты (бег, стрельба) будут описаны в базовом классе «Трансформер», а различия (трансформация) в двух дочерних классах «Автобот» и «Десептикон».
+
+```cs
+// базовый класс
+class Transformer
+{ 
+    public void run(){
+        // код, отвечающий за бег
+    }
+    public void fire(){
+        // код, отвечающий за стрельбу
+    }
+}
+
+// дочерний класс, наследование от Transformer
+class Autobot : Transformer
+{ 
+    public void transform(){
+        // код, отвечающий за трансформацию в автомобиль
+    }
+}
+
+// дочерний класс, наследование от Transformer
+class Decepticon : Transformer
+{ 
+    public void transform(){
+        // код, отвечающий за трансформацию в самолет
+    }
+}
+
+var optimus = new Autobot();
+var megatron = new Decepticon();
+```
+
+Этот пример наглядно иллюстрирует, как наследование становится одним из способов избежать дублирования кода с помощью родительского класса, и одновременно предоставляет возможности для мутации в классах-потомках.
+
+#### Перегрузка
+
+Если же в классе-потомке переопределить уже существующий в классе-родителе метод, то сработает перегрузка. Это позволяет не дополнять поведение родительского класса, а модифицировать. В момент вызова метода или обращения к полю объекта, поиск атрибута происходит от потомка к самому корню — родителю. То есть, если у автобота вызвать метод fire(), сначала поиск метода производится в классе-потомке — **Autobot**, а поскольку его там нет, поиск поднимается на ступень выше — в класс **Transformer**, где и будет обнаружен и вызван.
+
+### Полиморфизм
+
+**Полиморфизм** — свойство системы, позволяющее иметь множество реализаций одного интерфейса. Ничего непонятно. Обратимся к трансформерам.
+
+Положим, у нас есть три трансформера: *Оптимус*, *Мегатрон* и *Олег*. Трансформеры боевые, стало быть обладают методом *attack()*. Игрок, нажимая у себя на джойстике кнопку «воевать», сообщает игре, чтобы та вызвала метод *attack()* у трансформера, за которого играет игрок. Но поскольку трансформеры разные, а игра интересная, каждый из них будет атаковать каким-то своим способом. Скажем, Оптимус — объект класса Автобот, а Автоботы снабжаются пушками с плутониевыми боеголовками. Мегатрон — Десептикон, и стреляет из плазменной пушки. Олег — басист, и он обзывается. А в чем польза?
+
+Польза полиморфизма в данном примере заключается в том, что код игры ничего не знает о реализации его просьбы, кто как должен атаковать, его задача просто вызвать метод *attack()*, сигнатура которого одинакова для всех классов персонажей. Это позволяет добавлять новые классы персонажей, или менять методы существующих, не меняя код игры. Это удобно.
+
+### Инкапсуляция
+
+**Инкапсуляция** — это контроль доступа к полям и методам объекта. Под контролем доступа подразумевается не только можно/неможно, но и различные валидации, подгрузки, вычисления и прочее динамическое поведение.
+
+Во многих языках частью инкапсуляции является сокрытие данных. Для этого существуют модификаторы доступа (опишем те, которые есть почти во всех ООП языках):
+
+* **publiс** — к атрибуту может получить доступ любой желающий
+* **private** — к атрибуту могут обращаться только методы данного класса
+* **protected** — то же, что и **private**, только доступ получают и наследники класса в том числе
+
+```cs
+class Transformer 
+{
+    public Transformer(){ }
+
+    protected void setup(){ }
+
+    private void dance(){ }
+}
+```
+
+Как правильно выбрать модификатор доступа? В простейшем случае так: если метод должен быть доступен внешнему коду, выбираем **public**. В противном случае — **private**. Если есть наследование, то может потребоваться **protected** в случае, когда метод не должен вызываться снаружи, но должен вызываться потомками.
+
+## Абстрактные классы
+
+Кроме обычных классов в некоторых языках существуют абстрактные классы. От обычных классов они отличаются тем, что нельзя создать объект такого класса. Зачем же нужен такой класс, спросит читатель? Он нужен для того, чтобы от него могли наследоваться потомки — обычные классы, объекты которых уже можно создавать.
+
+Абстрактный класс наряду с обычными методами содержит в себе абстрактные методы без реализации (с названием, но без кода), которые обязан реализовать программист, задумавший создать класс-потомок. Абстрактные классы не обязательны, но они помогают установить контракт, обязующий реализовать определенный набор методов.
+
+## Интерфейсы
+
+Задача **интерфейса** — снизить уровень зависимости сущностей друг от друга, добавив больше абстракции.
+
+Выше мы рассматривали абстрактные классы, затрагивая тему контрактов, обязующих реализовать какие-то абстрактные методы. Так вот интерфейс очень смахивает на абстрактный класс, но является не классом, а просто пустышкой с перечислением абстрактных методов. 
+
+Интерфейсы в C#, однако, могут содержать как абстрактные методы, тик и методы с реализацией (начиная с версии 8.0).
+
+Обычно в языках, в которых есть интерфейсы, нет множественного наследования классов, но есть множественное наследование интерфейсов. Это позволяет классу перечислить интерфейсы, которые он обязуется имплементировать (реализовать).
+
+Классы с интерфейсами состоят в отношении «многие ко многим»: один класс может имплементировать множество интерфейсов, и каждый интерфейс, в свою очередь, может имплементироваться многими классами.
+
+У интерфейса двустороннее применение:
+
+* По одну сторону интерфейса — классы, реализующие данный интерфейс.
+* По другую сторону — потребители, которые используют этот интерфейс в качестве описания типа данных, с которым они (потребители) работают.
+
+Например, если какой-то объект помимо основного поведения, может быть сериализован, то пускай он имплементирует интерфейс «Сериализуемый». А если объект можно склонировать, то пусть он имплементирует еще один интерфейс — «Клонируемый». И если у нас есть какой-то транспортный модуль, который передает объекты по сети, он будет принимать любые объекты, имплементирующие интерфейс «Сериализуемый».
+
+Представим, что каркас трансформера оборудован тремя слотами: слот для оружия, для генератора энергии и для какого-нибудь сканера. Эти слоты обладают определенными интерфейсами: в каждый слот можно установить только подходящее оборудование. В слот для оружия можно установить ракетную установку или лазерную пушку, в слот для генератора энергии — ядерный реактор или РИТЭГ (радиоизотопный термоэлектрический генератор), а в слот для сканера — радар или лидар. Суть в том, что каждый слот имеет универсальный интерфейс подключения, а уже конкретные устройства должны соответствовать этому интерфейсу. К примеру, на материнских платах используется несколько типов слотов: слот для процессора позволяет подключать различные процессоры, подходящие под данный сокет, а слот SATA — любой SSD или HDD накопитель или даже CD/DVD.
+
+### Определение интерфейса
+
+Для определения интерфейса используется ключевое слово **interface**. Как правило, названия интерфейсов в C# начинаются с заглавной буквы "I", например, *IComparable*, *IEnumerable* (так называемая венгерская нотация), однако это не обязательное требование, а больше стиль программирования.
+
+```cs
+interface IWeapon
+{
+    void Fire(); // декларация метода без имплементации. Ниже аналогично
+}
+
+interface IEnergyGenerator
+{
+    // тут уже два метода, которые должны будут реализовать классы:
+    void GenerateEnergy(); // первый
+    void LoadFuel();       // второй
+}
+
+interface IScanner
+{
+    void Scan();
+}
+
+// классы, реализующие интерфейсы:
+
+class RocketLauncher : IWeapon
+{
+    public void Fire()
+    {
+        // имплементация запуска ракеты
+    }
+}
+
+class LaserGun : IWeapon
+{
+    public void Fire()
+    {
+        // имплементация выстрела лазером
+        Console.WriteLine("LaserGun fire");
+    }
+}
+
+class NuclearReactor : IEnergyGenerator
+{
+    public void GenerateEnergy()
+    {
+        // имплементация генерации энергии ядерным реактором
+    }
+
+    public void LoadFuel()
+    {
+        // имплементация загрузки урановых стержней
+    }
+}
+
+class RITEG : IEnergyGenerator
+{
+    public void GenerateEnergy()
+    {
+        // имплементация генерации энергии РИТЭГ
+    }
+
+    public void LoadFuel()
+    {
+        // имплементация загрузки РИТЭГ-пеллет
+    }
+}
+
+class Radar : IScanner
+{
+    public void Scan()
+    {
+        // имплементация использования радиолокации
+    }
+}
+
+class Lidar : IScanner
+{
+    public void Scan()
+    {
+        // имплементация использования оптической локации
+    }
+}
+
+// класс - потребитель:
+
+class Transformer: IWeapon
+{
+    // привет, композиция:
+    // Интерфейсы указаны в качестве типов данных.
+    // Они могут принимать любые объекты,
+    // которые имплементируют указанный интерфейс
+
+    private IWeapon SlotWeapon = null;
+    private IEnergyGenerator SlotEnergyGenerator = null;
+    private IScanner SlotScanner = null;
+
+    /*
+    в параметрах методов интерфейс тоже указан как тип данных,
+    метод может принимать объект любого класса,
+    имплементирующий данный интерфейс:
+    */
+    public void InstallWeapon(IWeapon weapon)
+    {
+        SlotWeapon = weapon;
+    }
+
+    public void InstallEnergyGenerator(IEnergyGenerator EnergyGenerator)
+    {
+        SlotEnergyGenerator = EnergyGenerator;
+    }
+
+    public void InstallScanner(IScanner Scanner)
+    {
+        SlotScanner = Scanner;
+    }
+
+    public void Fire()
+    {
+        if (SlotWeapon != null) SlotWeapon.Fire();
+    }
+}
+
+// фабрика трансформеров
+
+class TransformerFactory
+{
+    static public Transformer BuildSomeTransformer()
+    {
+        var transformer = new Transformer();
+        var laser_gun = new LaserGun();
+        var nuclear_reactor = new NuclearReactor();
+        var radar = new Radar();
+
+
+        transformer.InstallWeapon(laser_gun);
+        transformer.InstallEnergyGenerator(nuclear_reactor);
+        transformer.InstallScanner(radar);
+
+        return transformer;
+    }
+}
+
+// использование
+class Program
+{
+    static void Main(string[] args)
+    {
+        var oleg = TransformerFactory.BuildSomeTransformer();
+        oleg.Fire();
+
+        Console.ReadKey();
+    }
+}
+```
+
+![](../img/kotlin_001.gif)
+
+Cлой абстракции в виде интерфейсов между слоем реализации (класс) и слоем-потребителем дает возможность абстрагировать одних от других. Вы можете это наблюдать, посмотрев на каждый слой в отдельности: в слое реализации (слева) нет ни слова про класс **Transformer**, а в слое-потребителе (справа) нет ни слова про конкретные реализации (там нет слов **Radar**, **RocketLauncher**, **NuclearReactor** и т. д.)
+
+В таком коде мы можем создавать новые комплектующие к трансформерам, не затрагивая чертежи самих трансформеров. В то же время и наоборот, мы можем создавать новых трансформеров, комбинируя уже существующие комплектующие, либо добавлять новые комплектующие, не меняя существующих.
+
+<table style="width: 100%;"><tr><td style="width: 40%;">
+<a href="../articles/t6_oop1.md">История развития ООП. Базовые понятия: объект, его свойства и методы, класс, интерфейс. Основные принципы ООП: инкапсуляция, наследование, полиморфизм.
+</a></td><td style="width: 20%;">
+<a href="../readme.md">Содержание
+</a></td><td style="width: 40%;">
+<a href="../articles/t6_linq.md">LINQ.
+</a></td><tr></table>

+ 1603 - 0
articles/t6_templates.md

@@ -0,0 +1,1603 @@
+<table style="width: 100%;"><tr><td style="width: 40%;">
+<a href="../articles/t6_linq.md">LINQ
+</a></td><td style="width: 20%;">
+<a href="../readme.md">Содержание
+</a></td><td style="width: 40%;">
+<a href="../articles/t7_dll.md">Библиотеки классов
+</a></td><tr></table>
+
+# Шаблоны проектирования (порождающие)
+
+>содрано [отсюда](https://tproger.ru/translations/design-patterns-simple-words-1/) с переводом на C# и проверкой
+
+**Шаблоны проектирования** — это руководства по решению повторяющихся проблем. Это не классы, пакеты или библиотеки, которые можно было бы подключить к вашему приложению и сидеть в ожидании чуда. Они скорее являются методиками, как решать определенные проблемы в определенных ситуациях.
+
+Википедия описывает их следующим образом:
+
+Шаблон проектирования, или паттерн, в разработке программного обеспечения — повторяемая архитектурная конструкция, представляющая собой решение проблемы проектирования, в рамках некоторого часто возникающего контекста.
+
+## Будьте осторожны
+
+* шаблоны проектирования не являются решением всех ваших проблем;
+* не пытайтесь использовать их в обязательном порядке — это может привести к негативным последствиям. Шаблоны — это подходы к решению проблем, а не решения для поиска проблем;
+* если их правильно использовать в нужных местах, то они могут стать спасением, а иначе могут привести к ужасному беспорядку.
+
+## Типы шаблонов
+
+Шаблоны бывают следующих трех видов:
+
+* Порождающие
+* Структурные
+* Поведенческие
+
+## Порождающие шаблоны
+
+Этот тип особенно важен, когда система зависит не столько от наследования классов, сколько от [композиции](https://habr.com/ru/post/325478/) (композиция — это когда один объект предоставляет другому свою функциональность частично или полностью). Порождающие паттерны отвечают за создание объектов и позволяют системе быть независимой от типов этих самых объектов и от процесса порождения.
+
+В свою очередь, порождающие паттерны делятся на:
+
+* Singleton
+* Simple Factory
+* Factory Method
+* Abstract Factory
+* Builder
+* Prototype
+
+### Одиночка (Singleton)
+
+**Одиночка** — порождающий шаблон проектирования, гарантирующий, что в однопроцессном приложении будет единственный экземпляр некоторого класса, и предоставляющий глобальную точку доступа к этому экземпляру.
+
+**Пример из жизни**: В семье всего одна пара тапочек, одеть их может только один человек.
+
+**Простыми словами**: Обеспечивает тот факт, что создаваемый объект является единственным объектом своего класса.
+
+В C# он реализуется статическими свойствами класса (из темы про классы мы помним, что статические переменные определяются при первом обращении):
+
+```cs
+class Core
+{
+    public static SomeEntities DB = new SomeEntities();
+}
+```
+
+### Шаблон Simple Factory (Простая Фабрика)
+
+В объектно-ориентированном программировании (ООП), фабрика — это объект для создания других объектов. Формально фабрика — это функция или метод, который возвращает объекты изменяющегося прототипа или класса из некоторого вызова метода, который считается «новым».
+
+**Пример из жизни**: Представьте, что вам надо построить дом, и вам нужны двери. Было бы глупо каждый раз, когда вам нужны двери, надевать вашу столярную форму и начинать делать дверь. Вместо этого вы заказываете её на фабрике.
+
+**Простыми словами**: Простая фабрика генерирует экземпляр для клиента, не раскрывая никакой логики.
+
+Шаблон предназначен для инкапсуляции процесса образования объектов с помощью отдельного класса. «Простая Фабрика» удобна, но за простоту приходится платить: привязка к конкретной реализации исключает гибкость системы. *Simple Factory* следует использовать только там, где архитектура не будет изменяться.
+
+Допустим, у нас есть интерфейс двери и класс, реализующий деревянную дверь:
+
+```cs
+interface IDoor
+{
+    double GetWidth();
+    double GetHeight();
+}
+
+class WoodenDoor : IDoor 
+{
+    private double Width { get; set; }
+    private double Height { get; set; }
+
+    public WoodenDoor(double width, double height) {
+        Width = width;
+        Height = height;
+    }
+
+    public double GetWidth()
+    {
+        return Width;
+    }
+
+    public double GetHeight()
+    {
+        return Height;
+    }
+}
+```
+
+Далее появляется завод, который изготавливает дверь и возвращает её нам:
+
+```cs
+class DoorFactory 
+{ 
+    static public IDoor MakeDoor(double Width, double Height) {
+        return new WoodenDoor(Width, Height);
+    }
+}
+```
+
+И после этого мы можем сделать дверь на фабрике:
+
+```cs
+var door = DoorFactory.MakeDoor(100, 200);
+```
+
+Как видно из кода, вызвав статический метод `DoorFactory.MakeDoor(100, 200)` мы получили не экземпляр завода, а экземпляр двери.
+
+**Когда использовать**: Когда создание объекта — это не просто несколько присвоений, а какая-то логика, тогда имеет смысл создать отдельную фабрику вместо повторения одного и того же кода повсюду.
+
+### Строитель (Builder)
+
+**Строитель** — порождающий шаблон проектирования, который предоставляет способ создания составного объекта. Предназначен для решения проблемы антипаттерна «Телескопический конструктор».
+
+**Пример из жизни**: Представьте, что вы пришли в McDonalds и заказали конкретный продукт, например, БигМак, и вам готовят его без лишних вопросов. Это пример простой фабрики. Но есть случаи, когда логика создания может включать в себя больше шагов. Например, вы хотите индивидуальный сэндвич в Subway: у вас есть несколько вариантов того, как он будет сделан. Какой хлеб вы хотите? Какие соусы использовать? Какой сыр? В таких случаях на помощь приходит шаблон «Строитель».
+
+**Простыми словами**: Шаблон позволяет вам создавать различные виды объекта, избегая засорения конструктора. Он полезен, когда может быть несколько видов объекта или когда необходимо множество шагов, связанных с его созданием.
+
+Давайте я покажу на примере, что такое «Телескопический конструктор». 
+
+```kt
+class SomeClass {
+    constructor(size: Float, 
+                cheese: Boolean = true, 
+                pepperoni: Boolean = true, 
+                tomato: Boolean = false, 
+                lettuce: Boolean = true) {}
+}
+```    
+
+Как вы можете заметить, количество параметров конструктора может резко увеличиться, и станет сложно понимать расположение параметров. Кроме того, этот список параметров будет продолжать расти, если вы захотите добавить новые варианты. Это и есть «Телескопический конструктор».
+
+Перейдем к примеру в коде. Адекватной альтернативой будет использование шаблона «Строитель». Сначала у нас есть Бутерброд, который мы хотим создать:
+
+```kt
+class Burger {
+    protected var size: Int? = null
+    protected var cheese = false
+    protected var pepperoni = false
+    protected var lettuce = false
+    protected var tomato = false
+
+    constructor(builder: BurgerBuilder)
+    {
+        size = builder.size
+        cheese = builder.cheese
+        pepperoni = builder.pepperoni
+        lettuce = builder.lettuce
+        tomato = builder.tomato
+    }
+}
+```
+
+Аттрибуты бутерброда приватные, мы не будем его разбирать - употребим целиком.
+
+Затем мы берём «Строителя»:
+
+```kt
+class BurgerBuilder {
+    var size: Int? = null
+    var cheese = false
+    var pepperoni = false
+    var lettuce = false
+    var tomato = false
+
+    constructor(_size: Int) {
+        size = _size
+    }
+
+    fun addPepperoni(): BurgerBuilder {
+        pepperoni = true
+        return this
+    }
+
+    fun addLettuce(): BurgerBuilder {
+        lettuce = true
+        return this
+    }
+
+    fun addCheese(): BurgerBuilder {
+        cheese = true
+        return this
+    }
+
+    fun addTomato(): BurgerBuilder {
+        tomato = true
+        return this
+    }
+
+    fun build(): Burger {
+        return Burger(this)
+    }
+}
+```
+
+А вот у строителя аттрибуты публичные, т.к. используются при постронении бутерброда
+
+Пример использования:
+
+```kt
+fun main() {
+    var burger = BurgerBuilder(14)
+        .addPepperoni()
+        .addLettuce()
+        .addTomato()
+        .build()
+}
+```
+
+**Когда использовать**: Когда может быть несколько видов объекта и надо избежать «телескопического конструктора». Главное отличие от «фабрики» — это то, что она используется, когда создание занимает один шаг, а «строитель» применяется при множестве шагов.
+
+### Прототип (Prototype)
+
+Задаёт виды создаваемых объектов с помощью экземпляра-прототипа и создаёт новые объекты путём копирования этого прототипа. Он позволяет уйти от реализации и позволяет следовать принципу «программирование через интерфейсы». В качестве возвращающего типа указывается интерфейс / абстрактный класс на вершине иерархии, а классы-наследники могут подставить туда наследника, реализующего этот тип.
+
+**Пример из жизни**: Помните Долли? Овечка, которая была клонирована. Не будем углубляться, главное — это то, что здесь все вращается вокруг клонирования.
+
+**Простыми словами**: Прототип создает объект, основанный на существующем объекте при помощи клонирования.
+
+То есть он позволяет вам создавать копию существующего объекта и модернизировать его согласно вашим нуждам, вместо того, чтобы создавать объект заново.
+
+В Котлине легго клонировать объекты наследуя интерфейс **Cloneable** и переопределив его метод *clone()*:
+
+```kt
+class Sheep(var name: String) : Cloneable {
+    public override fun clone(): Sheep {
+        try {
+            return super.clone() as Sheep
+        } catch(e: CloneNotSupportedException) {
+            throw InternalError()
+        }
+    }
+}
+
+fun main() {
+    var original = Sheep("Jolly")
+    println( original.name ) // Jolly
+
+    // Clone and modify what is required
+    var cloned = original.clone()
+    cloned.name = "Dolly"
+    println(cloned.name) // Dolly
+}
+```
+
+### Шаблон Fabric Method (Фабричный метод)
+
+**Фабричный метод** — порождающий шаблон проектирования, предоставляющий подклассам интерфейс для создания экземпляров некоторого класса. В момент создания наследники могут определить, какой класс создавать. Иными словами, данный шаблон делегирует создание объектов наследникам родительского класса. Это позволяет использовать в коде программы не специфические классы, а манипулировать абстрактными объектами на более высоком уровне.
+
+**Пример из жизни**: Рассмотрим пример с менеджером по найму. Невозможно одному человеку провести собеседования со всеми кандидатами на все вакансии. В зависимости от вакансии он должен распределить этапы собеседования между разными людьми.
+
+**Простыми словами**: Менеджер предоставляет способ делегирования логики создания экземпляра дочерним классам.
+
+Изначально у нас есть интерфейс Interviewer и несколько реализаций для него:
+
+```kt
+interface Interviewer {
+    fun askQuestions()
+}
+
+class Developer : Interviewer {
+    override fun askQuestions() = println("Спрашивает про шаблоны проектирования!")
+}
+
+class CommunityExecutive : Interviewer {
+    override fun askQuestions() = println("Спрашивает о работе с сообществом")
+}
+```
+
+Теперь создаем менеджера по подбору персонала:
+
+```kt
+abstract class HiringManager {
+    lateinit var interviewer: Interviewer
+
+    // Фабричный метод
+    abstract fun makeInterviewer(): Interviewer
+
+    fun takeInterview() {
+        interviewer = makeInterviewer()
+        interviewer.askQuestions()
+    }
+}
+```
+
+И теперь любой дочерний класс может расширять его и предоставлять необходимого интервьюера:
+
+```kt
+class DevelopmentManager : HiringManager() {
+    override fun makeInterviewer(): Interviewer {
+        return Developer()
+    }
+}
+
+class MarketingManager : HiringManager() {
+    override fun makeInterviewer(): Interviewer {
+        return CommunityExecutive()
+    }
+}
+```
+
+После чего можно использовать:
+
+```kt
+fun main() {
+    var devManager = DevelopmentManager()
+    devManager.takeInterview() // Вывод: Спрашивает о шаблонах проектирования!
+
+    var marketingManager = MarketingManager()
+    marketingManager.takeInterview() // Вывод: Спрашивает о работе с сообществом
+}
+```
+
+**Когда использовать**: Полезен, когда есть некоторая общая обработка в классе, но необходимый подкласс динамически определяется во время выполнения. Иными словами, когда клиент не знает, какой именно подкласс ему может понадобиться.
+
+### Абстрактная фабрика (Abstract Factory)
+
+**Абстрактная фабрика** — порождающий шаблон проектирования, предоставляет интерфейс для создания семейств взаимосвязанных или взаимозависимых объектов, не специфицируя их конкретных классов. Шаблон реализуется созданием абстрактного класса Factory, который представляет собой интерфейс для создания компонентов системы (например, для оконного интерфейса он может создавать окна и кнопки). Затем пишутся классы, реализующие этот интерфейс.
+
+**Пример из жизни**: Расширим наш пример про двери из простой фабрики. В зависимости от ваших нужд вам понадобится деревянная дверь из одного магазина, железная дверь — из другого или пластиковая — из третьего. Кроме того, вам понадобится соответствующий специалист: столяр для деревянной двери, сварщик для железной двери и так далее. Как вы можете заметить, тут есть зависимость между дверьми.
+
+**Простыми словами**: Фабрика фабрик. Фабрика, которая группирует индивидуальные, но связанные/зависимые фабрики без указания их конкретных классов.
+
+Обратимся к коду. Используем пример про двери. Сначала у нас есть интерфейс Door и несколько его реализаций:
+
+```kt
+interface Door {
+    fun getDescription()
+}
+
+class WoodenDoor : Door {
+    override fun getDescription() = println("Я деревянная дверь")
+}
+
+class IronDoor : Door {
+    override fun getDescription() = println("Я железная дверь")
+}
+```
+
+Затем у нас есть несколько мастеров по установке для каждого типа дверей:
+
+```kt
+interface DoorFittingExpert {
+    fun getDescription()
+}
+
+class Welder : DoorFittingExpert {
+    override fun getDescription() = println("Я слесарь, работаю только с железными дверьми")
+}
+
+class Carpenter : DoorFittingExpert {
+    override fun getDescription() = println("Я столяр, работаю только с деревянными дверьми")
+}
+```
+
+Теперь нам нужна фабрика дверей, которая позволит нам создать семейство связанных объектов. То есть фабрика деревянных дверей предоставит нам деревянную дверь и эксперта по деревянным дверям. Аналогично для железных дверей:
+
+```kt
+interface DoorFactory {
+    fun makeDoor(): Door
+    fun makeFittingExpert(): DoorFittingExpert
+}
+
+// Деревянная фабрика вернет деревянную дверь и столяра
+class WoodenDoorFactory : DoorFactory {
+    override fun makeDoor(): Door {
+        return WoodenDoor()
+    }
+
+    override fun makeFittingExpert(): DoorFittingExpert {
+        return Carpenter()
+    }
+}
+
+// Железная фабрика вернет железную дверь и сварщика
+class IronDoorFactory : DoorFactory {
+    override fun makeDoor(): Door {
+        return IronDoor()
+    }
+
+    override fun makeFittingExpert(): DoorFittingExpert {
+        return Welder()
+    }
+}
+```
+
+Пример использования:
+
+```kt
+fun main() {
+    var woodenFactory = WoodenDoorFactory()
+
+    var door = woodenFactory.makeDoor()
+    var expert = woodenFactory.makeFittingExpert()
+
+    door.getDescription()  // Вывод: Я деревянная дверь
+    expert.getDescription() // Вывод: Я работаю только с деревянными дверями
+
+    // Аналогично для железной двери
+    var ironFactory = IronDoorFactory()
+
+    door = ironFactory.makeDoor()
+    expert = ironFactory.makeFittingExpert()
+
+    door.getDescription()  // Вывод: Я железная дверь
+    expert.getDescription() // Вывод: Я работаю только с железными дверями
+}
+```
+
+Как вы можете заметить, фабрика деревянных дверей инкапсулирует столяра и деревянную дверь, а фабрика железных дверей инкапсулирует железную дверь и слесаря. Это позволило нам убедиться, что для каждой двери мы получим нужного нам установщика.
+
+**Когда использовать**: Когда есть взаимосвязанные зависимости с не очень простой логикой создания.
+
+
+
+## Структурные шаблоны
+
+>содрано [отсюда](https://tproger.ru/translations/design-patterns-simple-words-2/)
+
+**Простыми словами**: Структурные шаблоны в основном связаны с композицией объектов, другими словами, с тем, как сущности могут использовать друг друга. Ещё одним объяснением было бы то, что они помогают ответить на вопрос «Как создать программный компонент?».
+
+Список структурных шаблонов проектирования:
+
+* адаптер (Adapter);
+* мост (Bridge);
+* компоновщик (Composite);
+* декоратор (Decorator);
+* фасад (Facade);
+* приспособленец (Flyweight);
+* заместитель (Proxy).
+
+### Адаптер (Adapter)
+
+**Адаптер** — структурный шаблон проектирования, предназначенный для организации использования функций объекта, недоступного для модификации, через специально созданный интерфейс.
+
+**Пример из жизни**: Представим, что у вас на карте памяти есть какие-то изображения и вам надо перенести их на ваш компьютер. Чтобы это сделать, вам нужен какой-то адаптер, который совместим с портами вашего компьютера. В этом случае карт-ридер — это адаптер. Другим примером будет блок питания. Вилку с тремя ножками нельзя вставить в розетку с двумя отверстиями. Для того, чтобы она подошла, надо использовать адаптер. Ещё одним примером будет переводчик, переводящий слова одного человека для другого.
+
+**Простыми словами**: Шаблон позволяет обернуть несовместимые объекты в адаптер, чтобы сделать их совместимыми с другим классом.
+
+Обратимся к коду. Представим игру, в которой охотник охотится на львов.
+
+Изначально у нас есть интерфейс Lion, который реализует всех львов:
+
+```kt
+interface Lion {
+    fun roar()
+}
+
+class AfricanLion : Lion {
+    override fun roar(){}
+}
+
+class AsianLion : Lion {
+    override fun roar(){}
+}
+```
+
+И Hunter охотится на любую реализацию интерфейса Lion:
+
+```kt
+class Hunter {
+    fun hunt(lion: Lion){}
+}
+```
+
+Теперь представим, что нам надо добавить WildDog в нашу игру, на которую наш Hunter также мог бы охотиться. Но мы не можем сделать это напрямую, потому что у WildDog другой интерфейс. Чтобы сделать её совместимой с нашим Hunter, нам надо создать адаптер:
+
+```kt
+// Это надо добавить в игру
+class WildDog {
+    fun bark(){}
+}
+
+// Адаптер, чтобы сделать WildDog совместимой с нашей игрой 
+class WildDogAdapter(var dog: WildDog) : Lion { 
+    override fun roar() { 
+        dog.bark() 
+    } 
+}
+
+fun main() {
+    var wildDogAdapter = WildDogAdapter(WildDog())
+
+    var hunter = Hunter()
+    hunter.hunt(wildDogAdapter)
+}
+```
+
+### Мост (Bridge)
+
+**Мост** — структурный шаблон проектирования, используемый в проектировании программного обеспечения чтобы разделять абстракцию и реализацию так, чтобы они могли изменяться независимо. Шаблон мост использует инкапсуляцию, агрегирование и может использовать наследование для того, чтобы разделить ответственность между классами.
+
+**Пример из жизни**: Представим, что у вас есть сайт с разными страницами, и вам надо разрешить пользователям менять их тему. Что вы будете делать? Создавать множественные копии каждой страницы для каждой темы или просто отдельную тему, которую пользователь сможет выбрать сам? Шаблон мост позволяет вам сделать второе.
+
+**Простыми словами**: Шаблон мост — это предпочтение композиции над наследованием. Детали реализации передаются из одной иерархии в другой объект с отдельной иерархией.
+
+Обратимся к примеру в коде. Возьмем пример с нашими страницами. У нас есть иерархия WebPage:
+
+```kt
+abstract class WebPage(open var theme: Theme){
+    abstract fun getContent(): String
+}
+
+class About(override var theme: Theme) : WebPage(theme) {
+    override fun getContent(): String = "Страница с информацией в ${theme.getColor()}"
+}
+
+class Careers(override var theme: Theme) : WebPage(theme) {
+    override fun getContent(): String = "Страница карьеры в ${theme.getColor()}"
+}
+```
+
+И отдельная иерархия Theme:
+
+```kt
+interface Theme {
+    fun getColor(): String
+}
+
+class DarkTheme : Theme {
+    override fun getColor() = "темной теме"
+}
+class LightTheme : Theme {
+    override fun getColor() = "светлой теме"
+}
+
+class AquaTheme : Theme {
+    override fun getColor() = "голубой теме"
+}
+```
+
+Применение в коде:
+
+```kt
+var darkTheme = DarkTheme()
+
+var about = About(darkTheme)
+var careers = Careers(darkTheme)
+
+println(about.getContent()) // "Страница информации в темной теме";
+println(careers.getContent()) // "Страница карьеры в темной теме";
+```
+
+### Компоновщик (Composite)
+
+**Компоновщик** — структурный шаблон проектирования, объединяющий объекты в древовидную структуру для представления иерархии от частного к целому. Компоновщик позволяет клиентам обращаться к отдельным объектам и к группам объектов одинаково. Паттерн определяет иерархию классов, которые одновременно могут состоять из примитивных и сложных объектов, упрощает архитектуру клиента, делает процесс добавления новых видов объекта более простым.
+
+**Пример из жизни**: Каждая организация скомпонована из сотрудников. У каждого сотрудника есть одинаковые свойства, такие как зарплата, обязанности, отчётность и т.д.
+
+**Простыми словами**: Шаблон компоновщик позволяет клиентам работать с индивидуальными объектами в едином стиле.
+
+Обратимся к коду. Возьмем наш пример с рабочими. У нас есть Employee (работники) разных типов:
+
+```kt
+interface Assignee {
+    fun canHandleTask(task: String): Boolean
+    fun takeTask(task: String)
+}
+
+class Employee(val name: String) : Assignee {
+    private var hasTask = false
+    override fun canHandleTask(task: String) = !hasTask
+
+    override fun takeTask(task: String) {
+        println("$name получил задачу: $task")
+        hasTask = true
+    }
+}
+
+class Team(val assignees: ArrayList<Assignee>) : Assignee {
+
+    // вспомогательные методы для управления композитом:
+    fun add(assignee: Assignee){
+        assignees.add(assignee)
+    }
+    fun remove(assignee: Assignee){}
+
+    override fun canHandleTask(task: String): Boolean {
+        for(assignee in assignees)
+            if (assignee.canHandleTask(task)) return true
+        return false
+    }
+
+    override fun takeTask(task: String) {
+        /* может быть разная имплементация - допустим, некоторые задания требуют
+        нескольких человек из команды одновременно
+        в простейшем случае берем первого незанятого работника среди assignees*/
+        var assignee = assignees.removeAt(0)
+        assignee.takeTask(task)
+    }
+}
+```
+
+Еще у нас есть Начальник:
+
+```kt
+class TaskManager(private val assignees: ArrayList<Assignee>) {
+    fun performTask(task: String) {
+        for(assignee in assignees)
+            if (assignee.canHandleTask(task)) {
+                assignee.takeTask(task)
+                return
+            }
+
+        throw Exception("Cannot handle the task - please hire more people")
+    }
+}
+```
+
+Способ применения (в моей реализации работники "однозадачные"):
+
+```kt
+fun main() {
+    var employee1 = Employee("трус")
+    var employee2 = Employee("балбес")
+    var employee3 = Employee("бывалый")
+    var employee4 = Employee("шурик")
+    var team1 = Team( arrayListOf<Assignee>(employee3, employee4) )
+
+    // ВНИМАНИЕ: передаем команду в taskManager как единый композит.
+    // Сам taskManager не знает, что это команда и работает с ней без модификации своей логики.
+    var taskManager = TaskManager( arrayListOf<Assignee>(employee1, employee2, team1) )
+
+    for(task in listOf("посадить дерево","построить дом","вырастить сына","украсть невесту","снять фильм"))
+        try {
+            taskManager.performTask( task )
+        } catch (e: Exception){
+            println("работники закончились")
+            break
+        }
+}
+```
+
+На выходе получим что-то подобное:
+
+```
+трус получил задачу: посадить дерево
+балбес получил задачу: построить дом
+бывалый получил задачу: вырастить сына
+шурик получил задачу: украсть невесту
+работники закончились
+```
+
+### Декоратор (Decorator)
+
+**Декоратор** — структурный шаблон проектирования, предназначенный для динамического подключения дополнительного поведения к объекту. Шаблон декоратор предоставляет гибкую альтернативу практике создания подклассов с целью расширения функциональности.
+
+**Пример из жизни**: Представим, что у вас есть свой кофейный автомат. Он умеет делать только простой кофе, но в двух вариантах: 100 мл и 200 мл. Так как стаканы стандартные, то вам пришла мысль, что для маленькой порции можно добавить опцию "добавить молоко". 
+
+**Простыми словами**: Шаблон декоратор позволяет вам динамически изменять поведение объекта во время работы, оборачивая их в объект класса декоратора.
+
+Перейдем к коду. Возьмем пример с кофе. Изначально у нас есть простой *CoffeeMachine* и реализующий его интерфейс *NormalCoffeeMachine*:
+
+```kt
+interface CoffeeMachine {
+    fun makeSmallCoffee()
+    fun makeLargeCoffee()
+}
+
+class NormalCoffeeMachine : CoffeeMachine {
+    override fun makeSmallCoffee() = println("обычный: делаю 100 мл")
+
+    override fun makeLargeCoffee() = println("обычный: делаю 200мл")
+}
+```
+
+Добавляем опцию с молоком:
+
+```kt
+//Decorator:
+class EnhancedCoffeeMachine(val coffeeMachine: CoffeeMachine) : CoffeeMachine by coffeeMachine {
+
+    // overriding behaviour
+    override fun makeLargeCoffee() {
+        println("улучшенный: делаю 200 мл")
+        coffeeMachine.makeLargeCoffee()
+    }
+
+    // extended behaviour
+    fun makeCoffeeWithMilk() {
+        println("улучшенный: делаю кофе с молоком")
+        coffeeMachine.makeSmallCoffee()
+        println("улучшенный: добавляю молоко")
+    }
+}
+```
+
+При описании класса *EnhancedCoffeeMachine* мы использовали [делегирование](https://kotlinlang.ru/docs/reference/delegation.html). Раньше мы про него не упоминали: класс Kotlin может реализовать интерфейс, делегируя его методы и свойства другому **объекту**, реализующему этот интерфейс. Это обеспечивает способ создания поведения с использованием ассоциации, а не наследования.
+
+В нашем случае мы в параметрах основного конструктора *EnhancedCoffeeMachine* получаем **экземпляр** класса реализующего интерфейс *CoffeeMachine*, сами тоже говорим, что реализуем интерфейс *CoffeeMachine*, но что-то поручаем делать **объекту** класса: ``CoffeeMachine by coffeeMachine``. И дальше по коду видно, что метод интерфейса *makeSmallCoffee()* в классе не реализован - он вызывается из **экземпляра** класса *NormalCoffeeMachine*. 
+
+А теперь приготовим кофе:
+
+```kt
+fun main(){
+    val normalMachine = NormalCoffeeMachine()
+    val enhancedMachine = EnhancedCoffeeMachine(normalMachine)
+
+    // не переопределенный метод - вызов делегируется
+    enhancedMachine.makeSmallCoffee()
+    // переопределенный метод со стандартной реализацией
+    enhancedMachine.makeLargeCoffee()
+    // расширенный метод - добавляем свой функционал
+    enhancedMachine.makeCoffeeWithMilk()
+}
+```
+
+```
+обычный: делаю 100 мл
+улучшенный: делаю 200 мл
+обычный: делаю 200 мл
+улучшенный: делаю кофе с молоком
+обычный: делаю 100 мл
+улучшенный: добавляю молоко
+```
+
+### Фасад (Facade)
+
+**Фасад** — структурный шаблон проектирования, позволяющий скрыть сложность системы путём сведения всех возможных внешних вызовов к одному объекту, делегирующему их соответствующим объектам системы.
+
+**Пример из жизни**: Как вы включаете компьютер? Нажимаю на кнопку включения, скажете вы. Это то, во что вы верите, потому что вы используете простой интерфейс, который компьютер предоставляет для доступа снаружи. Внутри же должно произойти гораздо больше вещей. Этот простой интерфейс для сложной подсистемы называется фасадом.
+
+**Простыми словами**: Шаблон фасад предоставляет упрощенный интерфейс для сложной системы.
+
+Перейдем к примерам в коде. 
+Изначально у нас есть класс ComplexSystemStore (сложное хранилище) и класс данных, которые нужно сохранять (data class):
+
+```kt
+class ComplexSystemStore(val filePath: String) {
+
+    init {
+        println("Reading data from file: $filePath")
+    }
+
+    val store = HashMap<String, String>()
+
+    fun store(key: String, payload: String) {
+        store.put(key, payload)
+    }
+
+    fun read(key: String): String = store[key] ?: ""
+
+    fun commit() = println("Storing cached data: $store to file: $filePath")
+}
+
+data class User(val login: String)
+```
+
+Теперь нарисуем класс *пользовательское хранилище* (фасад).
+
+```kt
+//Facade:
+class UserRepository {
+    val systemPreferences = ComplexSystemStore("/data/default.prefs")
+
+    fun save(user: User) {
+        systemPreferences.store("USER_KEY", user.login)
+        systemPreferences.commit()
+    }
+
+    fun findFirst(): User = User(systemPreferences.read("USER_KEY"))
+}
+```
+
+Пример использования:
+
+```kt
+fun main(){
+    val userRepository = UserRepository()
+    val user = User("dbacinski")
+    userRepository.save(user)
+    val resultUser = userRepository.findFirst()
+    println("Found stored user: $resultUser")
+}
+```
+
+```
+Reading data from file: /data/default.prefs
+Storing cached data: {USER_KEY=dbacinski} to file: /data/default.prefs
+Found stored user: User(login=dbacinski)
+```
+
+Таким образом мы скрываем от пользователя внутреннюю реализацию хранилища, пользователь имеет только две *крутилки*: сохранить и найти.
+
+### Приспособленец (Flyweight)
+
+**Приспособленец** — структурный шаблон проектирования, при котором объект, представляющий себя как уникальный экземпляр в разных местах программы, по факту не является таковым.
+
+**Пример из жизни**: Вы когда-нибудь заказывали чай в уличном ларьке? Там зачастуют готовят не одну чашку, которую вы заказали, а гораздо большую емкость. Это делается для того, чтобы экономить ресурсы (газ/электричество). Газ/электричество в этом примере и являются приспособленцами, ресурсы которых делятся (sharing).
+
+**Простыми словами**: Приспособленец используется для минимизации использования памяти или вычислительной стоимости путем разделения ресурсов с наибольшим количеством похожих объектов.
+
+Перейдем к примерам в коде. Возьмем наш пример с чаем. Изначально у нас есть различные виды Tea и TeaMaker:
+
+//TODO: добавить интерфейс Tea и несколько классов чая. Сделать уменьшение ресурса при заказе
+
+```kt
+// Все, что будет закешировано, является приспособленцем.
+// Типы чая здесь будут приспособленцами.
+class KarakTea
+
+//Ведет себя как фабрика и сохраняет чай
+class TeaMaker {
+    protected var availableTea = mutableMapOf<String, KarakTea>()
+
+    fun make(preference: String): KarakTea {
+        if(!availableTea.containsKey(preference))
+            availableTea.put(preference, KarakTea())
+
+        return availableTea[preference]!!
+    }
+}
+```
+
+Теперь у нас есть TeaShop, который принимает заказы и выполняет их:
+
+```kt
+class TeaShop(val teaMaker: TeaMaker) {
+    protected var orders = mutableMapOf<Int, KarakTea>()
+
+    fun takeOrder(teaType: String, table: Int) {
+        orders.put(table, teaMaker.make(teaType))
+    }
+
+    fun serve() {
+        for ((table, tea) in orders)
+            println("Serving tea ($tea) to table $table")
+    }
+}
+```
+
+Пример использования:
+
+```kt
+fun main(){
+    var shop = TeaShop( TeaMaker() );
+
+    shop.takeOrder("меньше сахара", 1)
+    shop.takeOrder("больше молока", 2)
+    shop.takeOrder("без сахара", 5)
+
+    shop.serve()
+}
+```
+
+В консоли получим примерно такое:
+
+```
+Serving tea (KarakTea@12edcd21) to table 1
+Serving tea (KarakTea@34c45dca) to table 2
+Serving tea (KarakTea@52cc8049) to table 5
+```
+
+### Заместитель (Proxy)
+
+**Заместитель** — структурный шаблон проектирования, который предоставляет объект, который контролирует доступ к другому объекту, перехватывая все вызовы (выполняет функцию контейнера).
+
+**Пример из жизни**: Вы когда-нибудь использовали карту доступа, чтобы пройти через дверь? Есть несколько способов открыть дверь: например, она может быть открыта при помощи карты доступа или нажатия кнопки, которая обходит защиту. Основная функциональность двери — это открытие, но заместитель, добавленный поверх этого, добавляет функциональность. Но лучше я объясню это на примере кода чуть ниже.
+
+**Простыми словами**: Используя шаблон заместитель, класс отображает функциональность другого класса.
+
+Перейдем к коду. Возьмем наш пример с безопасностью. Сначала у нас есть интерфейс Дверь и его реализация:
+
+```kt
+interface Door {
+    fun open()
+    fun close()
+}
+
+class LabDoor : Door {
+    override fun open() = println( "Открытие дверь лаборатории" )
+    override fun close() = println( "Закрытие двери лаборатории" )
+}
+```
+
+Затем у нас есть заместитель Security для защиты любых наших дверей:
+
+```kt
+class Security(val door: Door) {
+    fun open(password: String) {
+        if (authenticate(password))
+            door.open()
+        else
+            println( "Нет! Это невозможно." )
+    }
+
+    private fun authenticate(password: String) = password.equals("Secr@t")
+
+    fun close() {
+        door.close()
+    }
+}
+```
+
+Пример использования:
+
+```kt
+fun main() {
+    var door = Security( LabDoor() )
+    door.open("invalid")    // Нет! Это невозможно.
+
+    door.open("Secr@t")     // Открытие двери лаборатории
+    door.close() // Закрытие двери лаборатории
+}
+```
+
+> Пример простой и из него не понятно, почему бы классу Security просто не наследоваться от LabDoor и переопределить метод open. Но нужно учитывать, что дверей в здании может быть несколько и варианты доступа могут отличаться для разных типов (классов) дверей.
+
+
+## Поведенческие шаблоны
+
+>содрано [отсюда](https://tproger.ru/translations/design-patterns-simple-words-3/) с переводом на Котлин и проверкой
+
+Поведенческие шаблоны связаны с распределением обязанностей между объектами. Их отличие от структурных шаблонов заключается в том, что они не просто описывают структуру, но также описывают шаблоны для передачи сообщений / связи между ними. Или, другими словами, они помогают ответить на вопрос «Как запустить поведение в программном компоненте?»
+
+**Поведенческие шаблоны** — шаблоны проектирования, определяющие алгоритмы и способы реализации взаимодействия различных объектов и классов.
+
+Поведенческие шаблоны:
+
+* цепочка обязанностей (Chain of Responsibility);
+* команда (Command);
+* итератор (Iterator);
+* посредник (Mediator);
+* хранитель (Memento);
+* наблюдатель (Observer);
+* посетитель (Visitor);
+* стратегия (Strategy);
+* состояние (State);
+* шаблонный метод (Template Method).
+
+### Цепочка обязанностей (Chain of Responsibility)
+
+**Цепочка обязанностей** — поведенческий шаблон проектирования предназначенный для организации в системе уровней ответственности.
+
+**Пример из жизни**: Король орков отдает громкие приказы своей армии. Сначала реагирует командир, затем офицер, а затем солдат. Командир, офицер и солдат здесь образуют цепь ответственности.
+
+**Простыми словами**: цепочка обязанностей помогает строить цепочки объектов. Запрос входит с одного конца и проходит через каждый объект, пока не найдет подходящий обработчик.
+
+Обратимся к коду. 
+Приведем пример с формированием заголовка http-запроса. 
+
+Есть интерфейс *Цепочка Заголовков* и два класса, реализующих этот интерфейс.
+
+
+```kt
+interface HeadersChain {
+    fun addHeader(inputHeader: String): String
+}
+
+class AuthenticationHeader(val token: String?,
+                           var next: HeadersChain? = null) : HeadersChain {
+
+    override fun addHeader(inputHeader: String): String {
+        token ?: throw IllegalStateException("Token should be not null")
+        return inputHeader + "Authorization: Bearer $token\n"
+            .let { next?.addHeader(it) ?: it }
+    }
+}
+
+class ContentTypeHeader(val contentType: String,
+                        var next: HeadersChain? = null) : HeadersChain {
+
+    override fun addHeader(inputHeader: String): String =
+        inputHeader + "ContentType: $contentType\n"
+            .let { next?.addHeader(it) ?: it }
+}
+```
+
+Также есть класс для формирования тела запроса:
+
+```kt
+class BodyPayload(val body: String,
+                  var next: HeadersChain? = null) : HeadersChain {
+
+    override fun addHeader(inputHeader: String): String =
+        inputHeader + "$body"
+            .let { next?.addHeader(it) ?: it }
+}
+```
+
+Пример использования:
+
+```kt
+// создаем элементы цепочки
+val authenticationHeader = AuthenticationHeader("123456")
+val contentTypeHeader = ContentTypeHeader("json")
+val messageBody = BodyPayload("Body:\n{\n\"username\"=\"dbacinski\"\n}")
+
+// формируем цепочку
+authenticationHeader.next = contentTypeHeader
+contentTypeHeader.next = messageBody
+
+// формируем запрос с авторизацией
+val messageWithAuthentication =
+    authenticationHeader.addHeader("Headers with Authentication:\n")
+println(messageWithAuthentication)
+
+// формируем запрос без авторизации
+val messageWithoutAuth =
+    contentTypeHeader.addHeader("Headers:\n")
+println(messageWithoutAuth)
+```
+
+```
+Headers with Authentication:
+Authorization: Bearer 123456
+ContentType: json
+Body:
+{
+"username"="dbacinski"
+}
+
+Headers without Authentication:
+ContentType: json
+Body:
+{
+"username"="dbacinski"
+}
+```
+
+### Команда (Command)
+
+**Команда** — поведенческий шаблон проектирования, используемый при объектно-ориентированном программировании, представляющий действие. Объект команды заключает в себе само действие и его параметры.
+
+**Пример из жизни**: Типичный пример: вы заказываете еду в ресторане. Вы (т.е. Client) просите официанта (например, Invoker) принести еду (то есть Command), а официант просто переправляет запрос шеф-повару (то есть Receiver), который знает, что и как готовить. Другим примером может быть то, что вы (Client) включаете (Command) телевизор (Receiver) с помощью пульта дистанционного управления (Invoker).
+
+**Простыми словами**: Позволяет вам инкапсулировать действия в объекты. Основная идея, стоящая за шаблоном — это предоставление средств, для разделения клиента и получателя.
+
+Обратимся к коду. Изначально у нас есть получатель Bulb (лампочка), в котором есть реализация каждого действия, которое может быть выполнено:
+
+```kt
+// Получатель
+class Bulb {
+    fun turnOn() = println("Лампочка загорелась")
+    fun turnOff() = println("Темнота!")
+}
+```
+
+Затем у нас есть интерфейс Command, c набором команд:
+
+```kt
+interface Command {
+    fun execute()
+    fun undo()
+    fun redo()
+}
+```
+
+И классы, реализующие эти команды:
+
+```kt
+// Команда
+class TurnOn(protected var bulb: Bulb) : Command {
+    override fun execute() = bulb.turnOn()
+    override fun undo() = bulb.turnOff()
+    override fun redo() = execute()
+}
+
+class TurnOff(protected var bulb: Bulb) : Command {
+    override fun execute() = bulb.turnOff()
+    override fun undo() = bulb.turnOn()
+    override fun redo() = execute()
+}
+```
+
+И у нас есть Пульт, с которым клиент будет взаимодействовать для обработки любых команд:
+
+```kt
+// Invoker
+class RemoteControl {
+    fun submit(command: Command) = command.execute()
+}
+```
+
+Наконец, мы можем увидеть, как использовать нашего клиента:
+
+```kt
+fun main() {
+    var bulb = Bulb()
+
+    var turnOn = TurnOn(bulb)
+    var turnOff = TurnOff(bulb)
+
+    var remote = RemoteControl()
+    remote.submit(turnOn)   // Лампочка загорелась!
+    remote.submit(turnOff)  // Темнота!
+}
+```
+
+Шаблон **команда** может быть использован для реализации системы, основанной на транзакциях, где вы сохраняете историю команд, как только их выполняете. Если окончательная команда успешно выполнена, то все хорошо, иначе алгоритм просто перебирает историю и продолжает выполнять отмену для всех выполненных команд.
+
+### Итератор
+
+**Итератор** — поведенческий шаблон проектирования. Представляет собой объект, позволяющий получить последовательный доступ к элементам объекта-агрегата без использования описаний каждого из агрегированных объектов.
+
+Что-то не нашел примера под котлин, но вообще в котлине есть интерфейс Iterable и такой шаблон как-то отдельно реализовывать не нужно.
+
+
+### Посредник (Mediator)
+
+**Посредник** — поведенческий шаблон проектирования, обеспечивающий взаимодействие множества объектов, формируя при этом слабую связанность, и избавляя объекты, от необходимости явно ссылаться друг на друга.
+
+**Пример из жизни**: Общим примером будет, когда вы говорите с кем-то по мобильнику, то между вами и собеседником находится мобильный оператор. То есть сигнал передаётся через него, а не напрямую. В данном примере оператор — посредник.
+
+**Простыми словами**: Шаблон посредник подразумевает добавление стороннего объекта (посредника) для управления взаимодействием между двумя объектами (коллегами). Шаблон помогает уменьшить связанность (coupling) классов, общающихся друг с другом, ведь теперь они не должны знать о реализациях своих собеседников.
+
+Разберем пример в коде. Простейший пример: чат (посредник), в котором пользователи отправляют друг другу сообщения.
+
+Изначально у нас есть посредник ChatMediator:
+
+```kt
+class ChatMediator {
+
+    private val users: MutableList<ChatUser> = ArrayList()
+
+    fun sendMessage(msg: String, user: ChatUser) {
+        users
+            .filter { it != user }
+            .forEach {
+                it.receive(msg)
+            }
+    }
+
+    fun addUser(user: ChatUser): ChatMediator =
+        apply { users.add(user) }
+
+}
+```
+
+И собственно пользователи чата (User):
+
+```kt
+class ChatUser(private val mediator: ChatMediator, val name: String) {
+    fun send(msg: String) {
+        println("$name: Sending Message= $msg")
+        mediator.sendMessage(msg, this)
+    }
+
+    fun receive(msg: String) {
+        println("$name: Message received: $msg")
+    }
+}
+```
+
+Пример использования:
+
+```kt
+val mediator = ChatMediator()
+val john = ChatUser(mediator, "John")
+
+mediator
+    .addUser(ChatUser(mediator, "Alice"))
+    .addUser(ChatUser(mediator, "Bob"))
+    .addUser(john)
+john.send("Hi everyone!")
+```
+
+```
+John: Sending Message= Hi everyone!
+Alice: Message received: Hi everyone!
+Bob: Message received: Hi everyone!
+```
+
+### Хранитель (Memento)
+
+**Хранитель** — поведенческий шаблон проектирования, позволяющий, не нарушая инкапсуляцию, зафиксировать и сохранить внутреннее состояние объекта так, чтобы позднее восстановить его в этом состоянии.
+
+**Пример из жизни**: В качестве примера можно привести калькулятор (создатель), у которого любая последняя выполненная операция сохраняется в памяти (хранитель), чтобы вы могли снова вызвать её с помощью каких-то кнопок (опекун).
+
+**Простыми словами**: Шаблон хранитель фиксирует и хранит текущее состояние объекта, чтобы оно легко восстанавливалось.
+
+Обратимся к коду. Возьмем наш пример текстового редактора, который время от времени сохраняет состояние, которое вы можете восстановить.
+
+```kt
+// класс данных для сериализации состояния
+data class Memento(val state: String)
+
+class Originator(var state: String) {
+    fun createMemento() = Memento(state)
+
+    fun restore(memento: Memento) {
+        state = memento.state
+    }
+}
+
+class CareTaker {
+    private val mementoList = ArrayList<Memento>()
+
+    fun saveState(state: Memento) {
+        mementoList.add(state)
+    }
+
+    fun restore(index: Int): Memento {
+        return mementoList[index]
+    }
+}
+```
+
+Пример использования:
+
+
+```kt
+val originator = Originator("initial state")
+val careTaker = CareTaker()
+
+careTaker.saveState(originator.createMemento())
+
+originator.state = "State #1"
+originator.state = "State #2"
+
+careTaker.saveState(originator.createMemento())
+
+originator.state = "State #3"
+println("Current State: " + originator.state)
+
+originator.restore(careTaker.restore(1))
+println("Second saved state: " + originator.state)
+
+originator.restore(careTaker.restore(0))
+println("First saved state: " + originator.state)
+```
+
+```
+Current State: State #3
+Second saved state: State #2
+First saved state: initial state
+```
+
+### Наблюдатель (Observer)
+
+**Наблюдатель** — поведенческий шаблон проектирования, также известен как «подчинённые» (Dependents). Создает механизм у класса, который позволяет получать экземпляру объекта этого класса оповещения от других объектов об изменении их состояния, тем самым наблюдая за ними.
+
+**Пример из жизни**: Хороший пример: люди, ищущие работу, подписываются на публикации на сайтах вакансий и получают уведомления, когда появляются вакансии подходящие по параметрам.
+
+**Простыми словами**: Шаблон определяет зависимость между объектами, чтобы при изменении состояния одного из них зависимые от него узнавали об этом.
+
+```kt
+import kotlin.properties.Delegates
+
+// интерфейс для подписчиков
+interface TextChangedListener {
+    fun onTextChanged(oldText: String, newText: String)
+}
+
+// класс подписчика
+class PrintingTextChangedListener : TextChangedListener {
+    // при получении уведомления выведет сообщение
+    override fun onTextChanged(oldText: String, newText: String) {
+        println("Text is changed: \"$oldText\" -> \"$newText\"")
+    }
+}
+
+// класс, поддерживающий подписку
+class TextView {
+    // список подписчиков
+    private val listeners = mutableListOf<TextChangedListener>()
+
+    // сеттер для добавления подписчиков в список
+    var listener: TextChangedListener? = null
+        set(value){
+            if(value!=null) listeners.add(value)
+        }
+
+
+    // Delegates.observable() при изменении свойства выполняет лямбда-функцию
+    var text: String by Delegates.observable("<empty>") { _, old, new ->
+        listeners.forEach { 
+            it.onTextChanged(old, new) 
+        }
+    }
+}
+```
+
+>В этом примере можно обойтись без делегата, сделав сеттер для поля *text*:
+>
+>```kt
+>    var text = ""
+>        set(value){
+>            if(!text.equals(value)){
+>                listeners.forEach { it.onTextChanged(text, value) }
+>                field = value
+>            }
+>        }
+>```
+>
+>Здесь *field* - это так называемое **теневое** имя текущего свойства, если бы его не было, то программа попала бы в бесконечную рекурсю при попытке сохранить новое значение внутри сеттера.
+
+Ну и проверим как работает наш код:
+
+```kt
+val textView = TextView()
+
+// добавляем экземпляр подписчика
+textView.listener = PrintingTextChangedListener()
+
+// меняем свойство text
+with(textView) {
+    text = "Lorem ipsum"
+    text = "dolor sit amet"
+}
+```
+
+```
+Text is changed: "" -> "Lorem ipsum"
+Text is changed: "Lorem ipsum" -> "dolor sit amet"
+```
+
+### Посетитель (Visitor)
+
+**Посетитель** — поведенческий шаблон проектирования, описывающий операцию, которая выполняется над объектами других классов. При изменении *visitor* нет необходимости изменять обслуживаемые классы.
+
+**Пример из жизни**: Туристы собрались в Дубай. Сначала им нужен способ попасть туда (виза). После прибытия они будут посещать любую часть города, не спрашивая разрешения ходить где вздумается. Просто скажите им о каком-нибудь месте — и туристы могут там побывать. Шаблон посетитель помогает добавлять места для посещения.
+
+**Простыми словами**: Шаблон посетитель позволяет добавлять будущие операции для объектов без их модифицирования.
+
+//TODO: расписать что делает код
+
+```kt
+interface ReportVisitable {
+    fun <R> accept(visitor: ReportVisitor<R>): R
+}
+
+class FixedPriceContract(val costPerYear: Long) : ReportVisitable {
+    override fun <R> accept(visitor: ReportVisitor<R>): R = visitor.visit(this)
+}
+
+class TimeAndMaterialsContract(val costPerHour: Long, val hours: Long) : ReportVisitable {
+    override fun <R> accept(visitor: ReportVisitor<R>): R = visitor.visit(this)
+}
+
+class SupportContract(val costPerMonth: Long) : ReportVisitable {
+    override fun <R> accept(visitor: ReportVisitor<R>): R = visitor.visit(this)
+}
+
+interface ReportVisitor<out R> {
+    fun visit(contract: FixedPriceContract): R
+    fun visit(contract: TimeAndMaterialsContract): R
+    fun visit(contract: SupportContract): R
+}
+
+class MonthlyCostReportVisitor : ReportVisitor<Long> {
+    override fun visit(contract: FixedPriceContract): Long =
+        contract.costPerYear / 12
+
+    override fun visit(contract: TimeAndMaterialsContract): Long =
+        contract.costPerHour * contract.hours
+
+    override fun visit(contract: SupportContract): Long =
+        contract.costPerMonth
+}
+
+class YearlyReportVisitor : ReportVisitor<Long> {
+    override fun visit(contract: FixedPriceContract): Long =
+        contract.costPerYear
+
+    override fun visit(contract: TimeAndMaterialsContract): Long =
+        contract.costPerHour * contract.hours
+
+    override fun visit(contract: SupportContract): Long =
+        contract.costPerMonth * 12
+}
+```
+
+Пример работы:
+
+```kt
+fun main() {
+    val projects = arrayOf(
+        FixedPriceContract(costPerYear = 10000),
+        TimeAndMaterialsContract(hours = 150, costPerHour = 10),
+        SupportContract(costPerMonth = 500),
+        TimeAndMaterialsContract(hours = 50, costPerHour = 50))
+
+    val monthlyCostReportVisitor = MonthlyCostReportVisitor()
+
+    val monthlyCost = projects.map { it.accept(monthlyCostReportVisitor) }.sum()
+    println("Monthly cost: $monthlyCost")
+
+    val yearlyReportVisitor = YearlyReportVisitor()
+    val yearlyCost = projects.map { it.accept(yearlyReportVisitor) }.sum()
+    println("Yearly cost: $yearlyCost")
+}
+```
+
+```
+Monthly cost: 5333
+Yearly cost: 20000
+```
+
+### Стратегия (Strategy)
+
+**Стратегия** — поведенческий шаблон проектирования, предназначенный для определения семейства алгоритмов, инкапсуляции каждого из них и обеспечения их взаимозаменяемости. Это позволяет выбирать алгоритм путём определения соответствующего класса. Шаблон Strategy позволяет менять выбранный алгоритм независимо от объектов-клиентов, которые его используют.
+
+**Пример из жизни**: Возьмём пример с пузырьковой сортировкой. Мы её реализовали, но с ростом объёмов данных сортировка работа стала выполняться очень медленно. Тогда мы сделали быструю сортировку. Алгоритм работает быстрее на больших объёмах, но на маленьких он очень медленный. Тогда мы реализовали стратегию, при которой для маленьких объёмов данных используется пузырьковая сортировка, а для больших объёмов — быстрая.
+
+**Простыми словами**: Шаблон стратегия позволяет переключаться между алгоритмами или стратегиями в зависимости от ситуации.
+
+В примере класс для вывода (печати) строки принимающий в параметрах лямбда-функцию (стратегию) для предварительно обработки этой строки.
+
+```kt
+class Printer(private val stringFormatterStrategy: (String) -> String) {
+    fun printString(string: String) {
+        println(stringFormatterStrategy(string))
+    }
+}
+
+val lowerCaseFormatter: (String) -> String = { it.toLowerCase() }
+val upperCaseFormatter = { it: String -> it.toUpperCase() }
+```
+
+Пример использования:
+
+```kt
+fun main() {
+    val inputString = "LOREM ipsum DOLOR sit amet"
+
+    val lowerCasePrinter = Printer(lowerCaseFormatter)
+    lowerCasePrinter.printString(inputString)
+
+    val upperCasePrinter = Printer(upperCaseFormatter)
+    upperCasePrinter.printString(inputString)
+
+    val prefixPrinter = Printer { "Prefix: $it" }
+    prefixPrinter.printString(inputString)
+}
+```
+
+```
+lorem ipsum dolor sit amet
+LOREM IPSUM DOLOR SIT AMET
+Prefix: LOREM ipsum DOLOR sit amet
+```
+
+### Состояние (State)
+
+**Состояние** — поведенческий шаблон проектирования. Используется в тех случаях, когда во время выполнения программы объект должен менять своё поведение в зависимости от своего состояния.
+
+**Пример из жизни**: Допустим, в графическом редакторе вы выбрали кисть. Она меняет своё поведение в зависимости от настройки цвета, т. е. рисует линию выбранного цвета.
+
+**Простыми словами**: Шаблон позволяет менять поведение класса при изменении состояния.
+
+Перейдем к примерам в коде.
+Есть класс для авторизации пользователей (AuthorizationPresenter) и классы для состояния авторизайии: Unauthorized, Authorized. 
+
+```kt
+// изолированный класс - все его наследники должны быть объявлены в этом же файле
+sealed class AuthorizationState
+
+// object создает экземпляр и класс одновременно
+object Unauthorized : AuthorizationState()
+
+class Authorized(val userName: String) : AuthorizationState()
+
+class AuthorizationPresenter {
+
+    private var state: AuthorizationState = Unauthorized
+
+    val isAuthorized: Boolean
+        get() = when (state) {
+            is Authorized -> true
+            is Unauthorized -> false
+        }
+
+    val userName: String
+        get() {
+            val state = this.state //val enables smart casting of state
+            return when (state) {
+                is Authorized -> state.userName
+                is Unauthorized -> "Unknown"
+            }
+        }
+
+    fun loginUser(userName: String) {
+        state = Authorized(userName)
+    }
+
+    fun logoutUser() {
+        state = Unauthorized
+    }
+
+    override fun toString() = "User '$userName' is logged in: $isAuthorized"
+}
+```
+
+Пример работы:
+
+```kt
+fun main() {
+    val authorizationPresenter = AuthorizationPresenter()
+
+    authorizationPresenter.loginUser("admin")
+    println(authorizationPresenter)
+
+    authorizationPresenter.logoutUser()
+    println(authorizationPresenter)
+}
+```
+
+```
+User 'admin' is logged in: true
+User 'Unknown' is logged in: false
+```
+
+### Шаблонный метод (Template Method)
+
+**Шаблонный метод** — поведенческий шаблон проектирования, определяющий основу алгоритма и позволяющий наследникам переопределять некоторые шаги алгоритма, не изменяя его структуру в целом.
+
+**Пример из жизни**: Допустим, вы собрались строить дома. Этапы будут такими:
+
+* Подготовка фундамента.
+* Возведение стен.
+* Настил крыши.
+* Настил перекрытий.
+
+Порядок этапов никогда не меняется. Вы не настелите крышу до возведения стен и т. д. Но каждый этап модифицируется: стены, например, можно возвести из дерева, кирпича или газобетона.
+
+**Простыми словами**: Шаблонный метод определяет каркас выполнения определённого алгоритма, но реализацию самих этапов делегирует дочерним классам.
+
+Обратимся к коду. Допустим, у нас есть программный инструмент, позволяющий тестировать, проводить контроль качества кода, выполнять сборку, генерировать отчёты сборки (отчёты о покрытии кода, о качестве кода и т. д.), а также развёртывать приложение на тестовом сервере.
+
+Изначально у нас есть наш Builder, который описывает скелет для построения алгоритма:
+
+```kt
+abstract class Builder {
+    // Шаблонный метод
+    fun build() {
+        test()
+        lint()
+        assemble()
+        deploy()
+    }
+
+    abstract fun test()
+    abstract fun lint()
+    abstract fun assemble()
+    abstract fun deploy()
+}
+```
+
+Затем у нас есть его реализации:
+
+```kt
+class AndroidBuilder : Builder() {
+    override fun test() = println("Запуск Android тестов")
+    override fun lint() = println("Копирование Android кода")
+    override fun assemble() = println("Android сборка")
+    override fun deploy() = println("Развертывание сборки на сервере")
+}
+
+class IosBuilder : Builder() {
+    override fun test() = println("Запуск iOS тестов")
+    override fun lint() = println("Копирование iOS кода")
+    override fun assemble() = println("iOS сборка")
+    override fun deploy() = println("Развертывание сборки на сервере")
+}
+```
+
+Пример использования:
+
+```kt
+fun main() {
+    var androidBuilder = AndroidBuilder()
+    androidBuilder.build()
+
+    var iosBuilder = IosBuilder()
+    iosBuilder.build()
+}
+```
+
+```
+Запуск Android тестов
+Копирование Android кода
+Android сборка
+Развертывание сборки на сервере
+Запуск iOS тестов
+Копирование iOS кода
+iOS сборка
+Развертывание сборки на сервере
+```
+
+---
+
+<table style="width: 100%;"><tr><td style="width: 40%;">
+<a href="../articles/t6_linq.md">LINQ
+</a></td><td style="width: 20%;">
+<a href="../readme.md">Содержание
+</a></td><td style="width: 40%;">
+<a href="../articles/t7_dll.md">Библиотеки классов
+</a></td><tr></table>

+ 336 - 0
articles/t7_dll.md

@@ -0,0 +1,336 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Шаблоны проектирования.](./t6_templates.md) | [Содержание](../readme.md#тема-7-библиотеки-классов) | [Обзор типов оконных приложений в C#. Знакомство со структорой проекта. Компоновка.](./t8_win_app.md)
+
+# Библиотеки классов
+
+>DLL (англ. Dynamic Link Library — «библиотека динамической компоновки», «динамически подключаемая библиотека») в операционных системах Microsoft Windows и IBM OS/2 — динамическая библиотека, позволяющая многократное использование различными приложениями.
+
+## Disclaimer
+
+**Во-первых**, надо отметить, что "классические" **DLL** содержат **функции**. А **.NET**, как мы уже знаем, оперирует **класами** и **пространствами имён**. Поэтому **DLL**, создаваемые в **.NET** не совместимы с обычными. (Но если очень надо, то как-то это реализуется. Технология [Windows Forms](https://ru.wikipedia.org/wiki/Windows_Forms) использует [Win32 API](https://ru.wikipedia.org/wiki/Windows_API))
+
+**Во-вторых**, в рамках нашего курса вообще нет особой необходимости в **DLL** (просто не будет достаточно сложных проектов). 
+
+Но, на демо-экзамене и чемпионате в модуле *тестирование* может встретится задание написать библиотеку классов и unit-тесты для неё. Поэтому надо знать что это такое и уметь реализовать. 
+
+## Содержание
+
+<!-- https://metanit.com/sharp/tutorial/14.3.php -->
+
+* [Создание](#Создание)
+* [Подключение](#Подключение)
+* [Динамическая загрузка сборок и позднее связывание](#Динамическая-загрузка-сборок-и-позднее-связывание)
+
+Нередко различные классы и структуры оформляются в виде отдельных библиотек, которые компилируются в файлы **dll** и затем могут подключать в другие проекты. Благодаря этому мы можем определить один и тот же функционал в виде библиотеки классов и подключать в различные проекты или передавать на использование другим разработчикам.
+
+## Создание 
+
+1. Возьмем имеющийся проект приложения .NET, например, созданный в прошлых темах. 
+
+    ![](../img/rider_dll_01.png)
+
+    Запомним какую платформу (**.NET Core** или **.NET Framefork**) и версию .NET используем. 
+
+    Например, в моем случае **.NET Core** версии `net8.0`
+
+
+1. В структуре проекта нажмем правой кнопкой на название решения и далее в появившемся контекстном меню выберем **Add -> New Project...**
+
+    ![](../img/rider_dll_02.png)
+
+1. Далее в списке шаблонов найдем пункт **Class Library**. Будьте внимательны, в списке шаблонов несколько вариантов "Библиотек классов" под разные платформы. Создаваемая библиотека классов должна соответсвовать платформе и версии проекта, к которому её планируется подключать:
+
+    ![](../img/rider_dll_03.png)
+
+
+1. После создания проекта он появится в решении, в моем случае с названием `ClassLibrary1`:
+
+    ![](../img/rider_dll_04.png)
+
+1. По умолчанию новый проект имеет один пустой класс **Class1** в файле `Class1.cs`. Мы можем этот файл удалить или переименовать, как нам больше нравится.
+
+    ```cs
+    namespace ClassLibrary;
+
+    public class Class1
+    {
+    }
+    ```
+
+    Например, переименуем файл `Class1.cs` в `Person.cs`, а класс **Class1** в **Person**. Определим в классе **Person** простейший код:
+
+    ```cs
+    public class Person
+    {
+        public string name;
+        public int age;
+    }
+    ```
+
+    >**ОБРАТИТЕ ВНИМАНИЕ!!!** 
+    >
+    >IDE может создать класс без модификатора **public** - установите его, иначе не сможете использовать этот класс в других проектах.
+
+    Теперь скомпилируем библиотеку классов. Для этого нажмём правой кнопкой на проект библиотеки классов и в контекстном меню выберем пункт **Build Selected Projects**.
+
+    После компиляции библиотеки классов в папке проекта в каталоге `bin/Debug` мы сможем найти скомпилированный файл ***dll*** (`ClassLibrary.dll`).
+
+    ![](../img/rider_dll_05.png)
+
+## Подключение
+
+Подключим созданную библиотеку классов в основной проект. Для этого в основном проекте нажмем правой кнопкой на **Dependencies** (Зависимости) и в контекстном меню выберем пункт **Reference...**:
+
+Далее нам откроется окно для добавления зависимостей. Если Вы всё сделали правильно (библиотека находится в том же решении) то в этом будет пункт **Projects** со списком проектов в решениии, среди которых будет ваша библиотека (в моём случае `<ClassLibrary1>`).Поставим отметку рядом с нашей библиотекой.
+
+![](../img/rider_dll_06.png)
+
+Если наша библиотека вдруг представляет файл **dll**, который не связан ни с каким проектом в нашем решении, то с помощью кнопки **Add From** мы можем найти местоположение файла **dll** и также его подключить.
+
+После успешного подключения библиотеки в главном проекте изменим код, чтобы он использовал класс **Person** из библиотеки классов:
+
+```cs
+using ClassLibrary1;
+
+var tom = new Person { 
+    name = "Tom", 
+    age = 35 };
+
+Console.WriteLine(tom.name);
+```
+
+![](../img/rider_dll_07.png)
+
+Такое подключение библиотек назвается статическим.
+
+## Динамическая загрузка сборок и позднее связывание
+
+При создании приложения для него определяется набор сборок, которые будут использоваться. В проекте указываются ссылки на эти сборки, и когда приложение выполняется, при обращении к функционалу этих сборок они автоматически подгружаются.
+
+Но также мы можем сами динамически подгружать другие сборки, на которые в проекте нет ссылок.
+
+>Чтобы не делать новых библиотек, мы можем удалить ссылку на уже добавленную:
+>
+>![](../img/rider_dll_08.png)
+
+
+Для управления сборками в пространстве имен **System.Reflection** имеется класс **Assembly**. С его помощью можно загружать сборку, исследовать ее.
+
+Чтобы динамически загрузить сборку в приложение, надо использовать статические методы *Assembly.LoadFrom()* или *Assembly.Load()*.
+
+Метод *LoadFrom()* принимает в качестве параметра путь к сборке. Например, исследуем сборку на наличие в ней различных типов:
+
+>Чтобы не писать длинных относительных или, не дай бог, абсолютных путей нужно скопировать `ClassLibrary1.dll` в каталог `bin/Debug/<версия .net>/` (можно прямо в IDE)
+>
+>![](../img/rider_dll_09.png)
+
+```cs
+using System.Reflection;
+
+var asm = Assembly.LoadFrom(
+    "ClassLibrary1.dll");
+
+Console.WriteLine(asm.FullName);
+
+var types = asm.GetTypes();
+
+foreach(Type t in types)
+{
+    Console.WriteLine(t.Name);
+}
+```
+
+На выходе получим примерно такое:
+
+```
+ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
+Person
+
+Process finished with exit code 0.
+```
+
+В данном случае для исследования указывается библиотека `ClassLibrary1.dll`. Здесь использован относительный путь, так как сборка находится в одной папке с приложением.
+
+Метод *Load()* действует аналогично, только в качестве его параметра передается дружественное имя сборки, которое нередко совпадает с именем приложения: `Assembly asm = Assembly.Load("MyApp");`
+
+Получив все типы сборки с помощью метода *GetTypes()*, мы можем исследовать их или применить в своем проекте.
+
+### Исследование типов
+
+Класс **System.Type** представляет изучаемый тип, инкапсулируя всю информацию о нем. С помощью его свойств и методов можно получить эту информацию. Некоторые из его свойств и методов:
+
+* Метод **FindMembers()** возвращает массив объектов **MemberInfo** данного типа
+* Метод **GetConstructors()** возвращает все конструкторы данного типа в виде набора объектов **ConstructorInfo**
+* Метод **GetEvents()** возвращает все события данного типа в виде массива объектов **EventInfo**
+* Метод **GetFields()** возвращает все поля данного типа в виде массива объектов **FieldInfo**
+* Метод **GetInterfaces()** получает все реализуемые данным типом интерфейсы в виде массива объектов **Type**
+* Метод **GetMembers()** возвращает все члены типа в виде массива объектов **MemberInfo**
+* Метод **GetMethods()** получает все методы типа в виде массива объектов **MethodInfo**
+* Метод **GetProperties()** получает все свойства в виде массива объектов **PropertyInfo**
+* Свойство *Name* возвращает имя типа
+* Свойство *Assembly* возвращает название сборки, где определен тип
+* Свойство *Namespace* возвращает название пространства имен, где определен тип
+* Свойство *IsArray* возвращает **true**, если тип является массивом
+* Свойство *IsClass* возвращает **true**, если тип представляет класс
+* Свойство *IsEnum* возвращает **true**, если тип является перечислением
+* Свойство *IsInterface* возвращает **true**, если тип представляет интерфейс
+
+#### Получение типа
+
+
+
+Чтобы управлять типом и получать всю информацию о нем, нам надо сперва получить данный тип. Это можно сделать тремя способами: с помощью ключевого слова **typeof**, с помощью метода *GetType()* класса **Object** и применяя статический метод *Type.GetType()*.
+
+* Получение типа через **typeof**:
+
+    ```cs
+    Type myType = typeof(User);
+
+    Console.WriteLine(myType.ToString());
+    Console.ReadLine();
+    
+    public class User
+    {
+        public string Name { get; set; }
+        public int Age { get; set; }
+        public User(string n, int a)
+        {
+            Name = n;
+            Age = a;
+        }
+        public void Display()
+        {
+            Console.WriteLine($"Имя: {Name}  Возраст: {Age}");
+        }
+        public int Payment(int hours, int perhour)
+        {
+            return hours * perhour;
+        }
+    }
+    ```
+
+    Здесь определен класс **User** с некоторым набором свойств и полей. И чтобы получить его тип, используется выражение `Type myType = typeof(User);`
+
+* Получение типа с помощью метода *GetType* класса **Object**:
+
+    ```cs
+    User user = new User("Tom", 30);
+    Type myType = user.GetType();
+    ```
+
+    В отличие от предыдущего примера здесь, чтобы получить тип, надо создавать экземпляр класса.
+
+* И третий способ получения типа - статический метод *Type.GetType()*:
+
+    ```cs
+    Type myType = Type.GetType("TestConsole.User", false, true);
+    ```
+
+    Первый параметр указывает на полное имя класса с пространством имен. В данном случае класс **User** находится в пространстве имен **TestConsole**. Второй параметр указывает, будет ли генерироваться исключение, если класс не удастся найти. В данном случае значение **false** означает, что исключение не будет генерироваться. И третий параметр указывает, надо ли учитывать регистр символов в первом параметре. Значение **true** означает, что регистр игнорируется.
+
+    В данном случае класс основной программы и класс **User** находятся в одном проекте и компилируются в одну сборку exe. Однако может быть, что нужный нам класс находится в другой сборке dll. Для этого после полного имени класса через запятую указывается имя сборки:
+
+    ```cs
+    Type myType = Type.GetType("TestConsole.User, MyLibrary", false, true);
+    ```
+
+### Позднее связывание
+
+>Примеры из этого раздела я на `Rider` не переписывал - попробуйте сами
+
+С помощью динамической загрузки мы можем реализовать технологию **позднего связывания**. Позднее связывание позволяет создавать экземпляры некоторого типа, а также использовать его во время выполнения приложения.
+
+Использование позднего связывания менее безопасно в том плане, что при жестком кодировании всех типов (ранее связывание) на этапе компиляции мы можем отследить многие ошибки. В то же время позднее связывание позволяет создавать расширяемые приложения, когда дополнительный функционал программы неизвестен, и его могут разработать и подключить сторонние разработчики.
+
+Ключевую роль в позднем связывании играет класс **System.Activator**. С помощью его статического метода *Activator.CreateInstance()* можно создавать экземпляры заданного типа.
+
+Например, динамически загрузим сборку и вызовем у ней некоторый метод. Допустим, загружаемая сборка `MyApp.dll` представляет следующий класс:
+
+```cs
+class Program
+{
+    static void Main(string[] args)
+    {
+        Console.WriteLine(GetResult(6, 100, 2));
+ 
+        Console.ReadLine();
+    }
+ 
+    public static double GetResult(int percent, double capital, int year)
+    {
+        for (int i = 0; i < year; i++ )
+        {
+            capital += capital /100 * percent;
+        }
+        return capital;
+    }
+}
+```
+
+Итак, у нас стандартный класс **Program** с двумя методами. Теперь динамически подключим сборку с этой библиотекой в другой программе и вызовем ее методы.
+
+Пусть наша основная программа будет выглядеть так:
+
+```cs
+static void Main(string[] args)
+{
+    try
+    {
+        var asm = Assembly.LoadFrom(
+            "MyApp.dll");
+         
+        var t = asm.GetType(
+            "MyApp.Program", true, true);
+         
+        // создаем экземпляр класса Program
+        object obj = Activator.CreateInstance(t);
+         
+        // получаем метод GetResult
+        MethodInfo method = t.GetMethod("GetResult");
+         
+        // вызываем метод, передаем ему значения для параметров и получаем результат
+        object result = method.Invoke(obj, new object[] { 6, 100, 3 });
+        Console.WriteLine((result));
+    }
+    catch(Exception ex)
+    {
+        Console.WriteLine(ex.Message);
+    }
+    Console.ReadLine();
+}
+```
+
+Исследуемую сборку можно получить с помощью выражения `var asm = Assembly.LoadFrom("MyApp.dll")`.
+
+Затем с помощью метода *GetType* получаем тип - класс **Program**, который находится в сборке `MyApp.dll: var t = asm.GetType("MyApp.Program", true, true);`
+
+Получив тип, создаем его экземпляр: `object obj = Activator.CreateInstance(t)`. Результат создания - объект класса **Program** представляет собой переменную **obj**.
+
+И в конце остается вызвать метод. **Во-первых**, получаем сам метод: `MethodInfo method = t.GetMethod("GetResult");`. И потом с помощью метода *Invoke* вызываем его: `object result = method.Invoke(obj, new object[] { 6, 100, 3 })`. Здесь первый параметр представляет объект, для которого вызывается метод, а второй - набор параметров в виде массива **object[]**.
+
+Так как метод *GetResult* возвращает некоторое значение, то мы можем его получить из метода в виде объекта типа **object**.
+
+Если бы метод не принимал параметров, то вместо массива объектов использовалось бы значение null: `method.Invoke(obj, null)`
+
+В сборке `MyApp.dll` в классе **Program** также есть и другой метод - метод **Main**. Вызовем теперь его:
+
+```cs
+Console.WriteLine("Вызов метода Main");
+method = t.GetMethod("Main", BindingFlags.DeclaredOnly 
+    | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static);
+     
+method.Invoke(obj, new object[]{new string[]{}});
+```
+
+Так как метод Main является статическим и не публичным, то к нему используется соответствующая битовая маска `BindingFlags.NonPublic | BindingFlags.Static`. И поскольку он в качестве параметра принимает массив строк, то при вызове метода передается соответствующее значение: `method.Invoke(obj, new object[]{new string[]{}})`
+
+---
+
+## Задание:
+
+Реализуйте примеры из лекции используя классы из своей предметной области.
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Шаблоны проектирования.](./t6_templates.md) | [Содержание](../readme.md#тема-7-библиотеки-классов) | [Обзор типов оконных приложений в C#. Знакомство со структорой проекта. Компоновка.](./t8_win_app.md)

+ 699 - 0
articles/t8_binding.md

@@ -0,0 +1,699 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Ресурсы](./articles/wpf_resource.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Элементы управления](./t8_elements.md)
+
+
+# Привязка (Binding). Интерфейс INotifyPropertyChanged. Форматирование значений привязки и конвертеры значений.
+
+<!-- https://metanit.com/sharp/wpf/11.php -->
+
+## Введение в привязку данных
+
+В WPF привязка (**binding**) является мощным инструментом программирования, без которого не обходится ни одно серьёзное приложение.
+
+Привязка подразумевает взаимодействие двух объектов: источника и приемника. Объект-приемник создает привязку к определенному свойству объекта-источника. В случае модификации объекта-источника, объект-приемник также будет модифицирован. Например, простейшая форма с использованием привязки:
+
+```xml
+<StackPanel>
+    <TextBox 
+        x:Name="myTextBox" 
+        Height="30" />
+    <TextBlock 
+        x:Name="myTextBlock" 
+        Text="{Binding 
+            ElementName=myTextBox,
+            Path=Text}"
+        Height="30" />
+</StackPanel>
+```
+
+![](../img/08028.png)
+
+Для определения привязки используется выражение типа:
+
+```
+{Binding ElementName=Имя_объекта-источника, Path=Свойство_объекта-источника}
+```
+
+То есть в данном случае у нас элемент **TextBox** (поле ввода) является источником, а **TextBlock** (простой текст) - приемником привязки. Свойство *Text* элемента **TextBlock** привязывается к свойству *Text* элемента **TextBox**. В итоге при осуществлении ввода в текстовое поле синхронно будут происходить изменения в текстовом блоке.
+
+### Работа с привязкой в C#
+
+Ключевым объектом при создании привязки является объект **System.Windows.Data.Binding**. Используя этот объект мы можем получить уже имеющуюся привязку для элемента:
+
+```cs
+Binding binding = BindingOperations.GetBinding(myTextBlock, TextBlock.TextProperty);
+```
+
+В данном случае получаем привязку для свойства зависимостей _TextProperty_ элемента _myTextBlock_.
+
+Также можно полностью установить привязку в коде C#:
+
+```cs
+public MainWindow()
+{
+    InitializeComponent();
+  
+    Binding binding = new Binding();
+ 
+    // элемент-источник
+    binding.ElementName = "myTextBox"; 
+
+    // свойство элемента-источника
+    binding.Path = new PropertyPath("Text"); 
+
+    // установка привязки для элемента-приемника
+    myTextBlock.SetBinding(TextBlock.TextProperty, binding); 
+}
+```
+
+Если в дальнейшем нам станет не нужна привязка, то мы можем воспользоваться классом **BindingOperations** и его методами **ClearBinding()**(удаляет одну привязку) и **ClearAllBindings()** (удаляет все привязки для данного элемента)
+
+```cs
+BindingOperations.ClearBinding(myTextBlock, TextBlock.TextProperty);
+```
+
+или
+
+```cs
+BindingOperations.ClearAllBindings(myTextBlock);
+```
+
+Некоторые свойства класса **Binding**:
+
+* **ElementName**: имя элемента, к которому создается привязка
+* **IsAsync**: если установлено в **True**, то использует асинхронный режим получения данных из объекта. По умолчанию равно **False**
+* **Mode**: режим привязки
+* **Path**: ссылка на свойство объекта, к которому идет привязка
+* **TargetNullValue**: устанавливает значение по умолчанию, если привязанное свойство источника привязки имеет значение **null**
+* **RelativeSource**: создает привязку относительно текущего объекта
+* **Source**: указывает на объект-источник, если он не является элементом управления.
+* **XPath**: используется вместо свойства path для указания пути к xml-данным
+
+### Режимы привязки
+
+Свойство **Mode** объекта **Binding**, которое представляет режим привязки, может принимать следующие значения:
+
+* **OneWay**: свойство объекта-приемника изменяется после модификации свойства объекта-источника.
+* **OneTime**: свойство объекта-приемника устанавливается по свойству объекта-источника только один раз. В дальнейшем изменения в источнике никак не влияют на объект-приемник.
+* **TwoWay**: оба объекта - применки и источник могут изменять привязанные свойства друг друга.
+* **OneWayToSource**: объект-приемник, в котором объявлена привязка, меняет объект-источник.
+* **Default**: по умолчанию (если меняется свойство **TextBox.Text**, то имеет значение **TwoWay**, в остальных случаях **OneWay**).
+
+Применение режима привязки:
+
+```xml
+<StackPanel>
+    <TextBox 
+        x:Name="textBox1" 
+        Height="30" />
+    <TextBox 
+        x:Name="textBox2" 
+        Height="30" 
+        Text="{Binding 
+            ElementName=textBox1, 
+            Path=Text, 
+            Mode=TwoWay}" />
+</StackPanel>
+```
+
+### Обновление привязки. UpdateSourceTrigger
+
+Односторонняя привязка от источника к приемнику практически мгновенно изменяет свойство приемника. Но если мы используем двустороннюю привязку в случае с текстовыми полями (как в примере выше), то при изменении приемника свойство источника не изменяется мгновенно. Так, в примере выше, чтобы текстовое поле-источник изменилось, нам надо перевести фокус с текстового поля-приемника. И в данном случае в дело вступает свойство *UpdateSourceTrigger* класса **Binding**, которое задает, как будет присходить обновление. Это свойство в качестве принимает одно из значений перечисления **UpdateSourceTrigger**:
+
+* **PropertyChanged**: источник привязки обновляется сразу после обновления свойства в приемнике
+* **LostFocus**: источник привязки обновляется только после потери фокуса приемником
+* **Explicit**: источник не обновляется до тех пор, пока не будет вызван метод **BindingExpression.UpdateSource()**
+* **Default**: значение по умолчанию. Для большинства свойств это значение **PropertyChanged**. А для свойства **Text** элемента **TextBox** это значение **LostFocus**
+
+В данном случае речь идет об обновлении источника привязки после изменения приемника в режимах **OneWayToSource** или **TwoWay**. То есть чтобы у нас оба текстовых поля, которые связаны режимом **TwoWay**, моментально обновлялись после изменения одного из них, надо использовать значение **UpdateSourceTrigger.PropertyChanged**:
+
+```xml
+<StackPanel>
+    <TextBox 
+        x:Name="textBox1" 
+        Height="30" />
+    <TextBox 
+        x:Name="textBox2" 
+        Height="30"
+        Text="{Binding 
+            ElementName=textBox1, 
+            Path=Text, 
+            Mode=TwoWay, 
+            UpdateSourceTrigger=PropertyChanged}" />
+</StackPanel>
+```
+
+### Свойство Source
+
+>Модель WPF предлагает очень удобный функционал: возможность хранить данные как ресурс, локально для элемента управления, локально для всего окна либо глобально для всего приложения. Данные могут быть любыми по факту, начиная от текущей информации, заканчивая иерархией элементов WPF. Это позволяет разместить данные в одном месте и после этого использовать их в разных местах, что может пригодится при разработке.
+>
+>Этот функционал часто используется для работы со стилями и шаблонами, которые мы еще будем обсуждать в руководстве, но, как будет показано в этой главе - область применения ресурсов очень широкая. 
+>
+>Ресурсы в WPF имеют ключ (атрибут `x:Key`), с помощью которого становится возможным сослаться на эти ресурсы из любой другой части приложения, используя ключ с выражением разметки _StaticResource_. В этом примере я просто сохранил строку в ресурсах, которую позже использовал в двух разных элементах **TextBlock**.
+
+Свойство **Source** позволяет установить привязку даже к тем объектам, которые не являются элементами управления WPF. Например, определим класс **Phone**:
+
+```cs
+class Phone
+{
+    public string Title { get; set; }
+    public string Company { get; set; }
+    public int Price { get; set; }
+}
+```
+
+Теперь создадим объект этого класса и определим к нему привязку:
+
+```xml
+<Window.Resources>
+    <local:Phone 
+        x:Key="nexusPhone" 
+        Title="Nexus X5" 
+        Company="Google" 
+        Price="25000" />
+</Window.Resources>
+<Grid Background="Black">
+    <Grid.RowDefinitions>
+        <RowDefinition />
+        <RowDefinition />
+    </Grid.RowDefinitions>
+    <Grid.ColumnDefinitions>
+        <ColumnDefinition />
+        <ColumnDefinition />
+    </Grid.ColumnDefinitions>
+    <TextBlock 
+        Text="Модель:" 
+        Foreground="White"/>
+    <TextBlock 
+        x:Name="titleTextBlock" 
+        Text="{Binding 
+            Source={StaticResource nexusPhone}, 
+            Path=Title}"
+        Foreground="White" 
+        Grid.Column="1"/>
+    <TextBlock 
+        Text="Цена:" 
+        Foreground="White" 
+        Grid.Row="1"/>
+    <TextBlock 
+        x:Name="priceTextBlock" 
+        Text="{Binding 
+            Source={StaticResource nexusPhone}, 
+            Path=Price}"
+        Foreground="White" 
+        Grid.Column="1" 
+        Grid.Row="1"/>
+</Grid>
+```
+
+В примере выше ресурсы расположены на уровне окна (Window), так, что будем в состоянии их использовать с любого места в окне.
+
+Если Вы нуждаетесь в ресурсе лишь для одного выбранного элемента - можете сделать это локально, путем добавления ресурса к элементу управления, а не всему окну. Это работает так же, как и ресурсы для окна, разница состоит в том, можно ли будет "достучаться" до них с уровня элемента, в котором вы сохранили ресурс.
+
+```xml
+<StackPanel>
+    <StackPanel.Resources>
+        <sys:String 
+            x:Key="ComboBoxTitle">Items:
+        </sys:String>
+    </StackPanel.Resources>
+    <Label 
+        Content="{StaticResource ComboBoxTitle}" />
+</StackPanel>
+```
+
+В этом случае, мы добавили ресурс в **StackPanel** и, после, использовали его с уровня дочернего элемента - **Label**. Другие элементы внутри **StackPanel** также смогут его использовать, как и дочерние элементы **Label**. А вот элементы вне **StackPanel** не будут иметь доступа к введенным ресурсам.
+
+Если Вам необходимо иметь доступ к ресурсу с разных окон - это тоже возможно. Файл `App.xaml` может содержать ресурсы так же как и окна (и другие типы элементов). Но когда Вы храните ресурсы в этом файле, то они становятся глобально доступными во всех окнах и UserControl'ах в проекте. Это работает также как и при хранении ресурсов в Window:
+
+```xml
+<Application ...>
+    <Application.Resources>
+        <sys:String
+            x:Key="ComboBoxTitle">
+            Items:
+        </sys:String>
+    </Application.Resources>
+</Application>
+```
+
+### Свойство TargetNullValue
+
+На случай, если свойство в источнике привязки вдруг имеет значение **null**, то есть оно не установлено, мы можем задать некоторое значение по умолчанию. Например:
+
+```xml
+<Window.Resources>
+    <local:Phone 
+        x:Key="nexusPhone" 
+        Company="Google" 
+        Price="25000" />
+</Window.Resources>
+<StackPanel>
+    <TextBlock 
+        x:Name="titleTextBlock"
+        Text="{Binding 
+            Source={StaticResource nexusPhone}, 
+            Path=Title, 
+            TargetNullValue=Текст по умолчанию}" />
+</StackPanel>
+```
+
+В данном случае у ресурса *nexusPhone* не установлено свойство **Title**, поэтому текстовый блок будет выводить значение по умолчанию, указанное в параметре **TargetNullValue**.
+
+### Свойство RelativeSource
+
+Свойство **RelativeSource** позволяет установить привязку относительно элемента-источника, который связан какими-нибудь отношениями с элементом-приемником. Например, элемент-источник может быть одним из внешних контейнеров для элемента-приемника. Либо источником и приемником может быть один и тот же элемент.
+
+Для установки этого свойства используется одноименный объект **RelativeSource**. У этого объекта есть свойство **Mode**, которое задает способ привязки. Оно принимает одно из значений перечисления **RelativeSourceMode**:
+
+* **Self**: привязка осуществляется к свойству этого же элемента. То есть элемент-источник привязки в то же время является и приемником привязки.
+* **FindAncestor**: привязка осуществляется к свойству элемента-контейнера.
+
+Например, совместим источник и приемник привязке в самом элементе:
+
+```xml
+<TextBox Text="{Binding 
+    RelativeSource={RelativeSource Mode=Self}, 
+    Path=Background, 
+    Mode=TwoWay, 
+    UpdateSourceTrigger=PropertyChanged}" />
+```
+
+![](../img/08029.png)
+
+Здесь текст и фоновый цвет текстового поля связаны двусторонней привязкой. В итоге мы можем увидеть в поле числовое значение цвета, поменять его, и вместе с ним изменится и фон поля.
+
+Привязка к свойствам контейнера:
+
+```xml
+<Grid Background="Black">
+    <TextBlock 
+        Foreground="White"
+        Text="{Binding 
+            RelativeSource={RelativeSource 
+                Mode=FindAncestor,
+                AncestorType={x:Type Grid}}, 
+            Path=Background}" />
+</Grid>
+```
+
+При использовании режима **FindAncestor**, то есть привязке к контейнеру, необходимо еще указывать параметр **AncestorType** и передавать ему тип контейнера в виде выражения `AncestorType={x:Type Тип_элемента-контейнера}`. При этом в качестве контейнера мы могли бы выбрать любой контейнер в дереве элементов, в частности, в данном случае кроме **Grid** таким контейнером также является элемент **Window**.
+
+### Свойство DataContext
+
+У объекта **FrameworkElement**, от которого наследуются элементы управления, есть интересное свойство **DataContext**. Оно позволяет задавать для элемента и вложенных в него элементов некоторый контекст данных. Тогда вложенные элементы могут использовать объект **Binding** для привязки к конкретным свойствам этого контекста. Например, используем ранее определенный класс **Phone** и создадим контекст данных из объекта этого класса:
+
+Для WPF этого достаточно, а для Avalonia ещё нужно добавить тип данных: `x:DataType="local:Phone"`
+
+```xml
+<Window.Resources>
+    <local:Phone 
+        x:Key="nexusPhone" 
+        Title="Nexus X5" 
+        Company="Google" 
+        Price="25000" />
+</Window.Resources>
+<Grid 
+    DataContext="{StaticResource nexusPhone}" 
+    x:DataType="local:Phone"
+>
+    <Grid.ColumnDefinitions>
+        <ColumnDefinition />
+        <ColumnDefinition />
+        <ColumnDefinition />
+    </Grid.ColumnDefinitions>
+    <Grid.RowDefinitions>
+        <RowDefinition />
+        <RowDefinition />
+    </Grid.RowDefinitions>
+    <TextBlock 
+        Text="Модель" />
+    <TextBlock 
+        Text="{Binding Title}" 
+        Grid.Row="1" />
+    <TextBlock 
+        Text="Производитель" 
+        Grid.Column="1"/>
+    <TextBlock 
+        Text="{Binding Company}" 
+        Grid.Column="1" 
+        Grid.Row="1" />
+    <TextBlock 
+        Text="Цена" 
+        Grid.Column="2" />
+    <TextBlock 
+        Text="{Binding Price}" 
+        Grid.Column="2" 
+        Grid.Row="1" />
+</Grid>
+```
+
+Таким образом мы задаем свойству **DataContext** некоторый динамический или статический ресурс. Затем осуществляем привязку к этому ресурсу.
+
+## Интерфейс INotifyPropertyChanged.
+
+Выше использовался объект **Phone** для привязки к текстовым блокам. Однако если мы изменим его, содержимое текстовых блоков не изменится. Например, добавим в окно приложения кнопку:
+
+```xml
+<Window.Resources>
+    <local:Phone 
+        x:Key="nexusPhone" 
+        Title="Nexus X5" 
+        Company="Google" 
+        Price="25000" />
+</Window.Resources>
+<Grid 
+    DataContext="{StaticResource nexusPhone}" 
+    x:DataType="local:Phone"
+    >
+    <Grid.ColumnDefinitions>
+        <ColumnDefinition />
+        <ColumnDefinition />
+        <ColumnDefinition />
+    </Grid.ColumnDefinitions>
+    <Grid.RowDefinitions>
+        <RowDefinition />
+        <RowDefinition />
+        <RowDefinition />
+    </Grid.RowDefinitions>
+    <TextBlock 
+        Text="Модель" />
+    <TextBlock 
+        Text="{Binding Title}" 
+        Grid.Row="1" />
+    <TextBlock 
+        Text="Производитель" 
+        Grid.Column="1"/>
+    <TextBlock 
+        Text="{Binding Company}" 
+        Grid.Column="1" 
+        Grid.Row="1" />
+    <TextBlock 
+        Text="Цена" 
+        Grid.Column="2" />
+    <TextBlock 
+        Text="{Binding Price}" 
+        Grid.Column="2" 
+        Grid.Row="1" />
+
+    <Button 
+        Content="Изменить" 
+        Click="Button_Click" 
+        Grid.Column="2" 
+        Grid.Row="2" />
+</Grid>
+```
+
+И в файле кода для этой кнопки определим обработчик, в котором будет меняться свойства ресурса:
+
+```cs
+private void Button_Click(object sender, RoutedEventArgs e)
+{
+    Phone phone = (Phone)this.Resources["nexusPhone"];
+    phone.Company = "LG"; // Меняем с Google на LG
+}
+```
+
+Сколько бы мы не нажимали на кнопку, текстовые блоки, привязанные к ресурсу, не изменятся. Чтобы объект мог полноценно реализовать механизм привязки, нам надо реализовать в его классе интерфейс **INotifyPropertyChanged**. И для этого изменим класс **Phone** следующим образом:
+
+```cs
+using System.ComponentModel;
+ 
+class Phone : INotifyPropertyChanged
+{
+    private string title;
+    private string company;
+    private int price;
+ 
+    public string Title
+    {
+        get { return title; }
+        set
+        {
+            title = value;
+            OnPropertyChanged("Title");
+        }
+    }
+    public string Company
+    {
+        get { return company; }
+        set
+        {
+            company = value;
+            OnPropertyChanged("Company");
+        }
+    }
+    public int Price
+    {
+        get { return price; }
+        set
+        {
+            price = value;
+            OnPropertyChanged("Price");
+        }
+    }
+ 
+    public event PropertyChangedEventHandler PropertyChanged;
+    public void OnPropertyChanged(string prop = "")
+    {
+        if (PropertyChanged != null)
+            PropertyChanged(this, new PropertyChangedEventArgs(prop));
+    }
+}
+```
+
+Когда объект класса изменяет значение свойства, то он через событие **PropertyChanged** извещает систему об изменении свойства. А система обновляет привязанные объекты.
+
+## Форматирование значений привязки и конвертеры значений
+
+### Форматирование значений
+
+Привязка представляет очень простой механизм, однако иногда этому механизму требуется некоторая кастомизация (настройка). Так, нам может потребоваться небольшое форматирование значения. Для примера возьмем класс **Phone**:
+
+```cs
+class Phone
+{
+    public string Title { get; set; }
+    public string Company { get; set; }
+    public int Price { get; set; }
+}
+```
+
+Допустим, нам надо в текстовый блок вывести не только цену, но и еще какой-нибудь текст:
+
+```xml
+<Window.Resources>
+    <local:Phone 
+        x:Key="nexusPhone" 
+        Title="Nexus X5" 
+        Company="Google" 
+        Price="25000" />
+</Window.Resources>
+<Grid>
+    <TextBlock 
+        Text="{Binding 
+            StringFormat=Итоговая цена {0} рублей, 
+            Source={StaticResource nexusPhone}, 
+            Path=Price}" />
+</Grid>
+```
+
+![](../img/08030.png)
+
+Свойство **StringFormat** получает набор параметров в фигурных скобках. Фигурные скобки (`{0}`) передают собственно то значение, к которому идет привязка. Можно сказать, что действие свойства **StringFormat** аналогично методу *String.Format()*, который выполняет форматирование строк.
+
+При необходимости мы можем использовать дополнительные опции форматирования, например, `{0:C}` для вывода валюты, `{0:P}` для вывода процентов и т.д.:
+
+```xml
+<TextBlock 
+    Text="{Binding 
+        StringFormat={}{0:C}, 
+        Source={StaticResource nexusPhone}, 
+        Path=Price}" />
+```
+
+При этом если у нас значение в **StringFormat** начинается с фигурных скобок, например, `{0:C}`, то перед ними ставятся еще пара фигурных скобок, как в данном случае. По сути они ничего важно не несут, просто служат для корректной интерпретации строки.
+
+Либо в этом случае нам надо экранировать скобки слешами:
+
+```xml
+<TextBlock 
+    Text="{Binding 
+        StringFormat=\{0:C\}, 
+        Source={StaticResource nexusPhone}, 
+        Path=Price}" />
+```
+
+В зависимости от типа элемента доступны различные типы форматировщиков значений:
+
+* **StringFormat**: используется для класса **Binding**
+
+* **ContentStringFormat**: используется для классов **ContentControl**, **ContentPresenter**, **TabControl**
+
+* **ItemStringFormat**: используется для класса **ItemsControl**
+
+* **HeaderStringFormat**: используется для класса **HeaderContentControl**
+
+* **ColumnHeaderStringFormat**: используется для классов **GridView**, **GridViewHeaderRowPresenter**
+
+* **SelectionBoxItemStringFormat**: используется для классов **ComboBox**, **RibbonComboBox**
+
+Их применение аналогично. Например, так как **Button** представляет **ContentControl**, то для этого элемента надо использовать **ContentStringFormat**:
+
+```xml
+<Button 
+    ContentStringFormat="{}{0:C}"
+    Content="{Binding 
+        Source={StaticResource nexusPhone}, 
+        Path=Price}" />
+```
+
+### Конвертеры значений
+
+Конвертеры значений (_value converter_) также позволяют преобразовать значение из источника привязки к типу, который понятен приемнику привязки. Так как не всегда два связываемых привязкой свойства могут иметь совместимые типы. И в этом случае как раз и нужен конвертер значений.
+
+Допустим, нам надо вывести дату в определенном формате. Для этой задачи создадим в проекте класс конвертера значений:
+
+```cs
+public class DateTimeToDateConverter : IValueConverter
+{
+    public object Convert(
+        object value, 
+        Type targetType, 
+        object parameter, 
+        CultureInfo culture)
+    {
+        return ((DateTime)value).ToString("dd.MM.yyyy");
+    }
+     
+    public object ConvertBack(
+        object value, 
+        Type targetType, 
+        object parameter, 
+        CultureInfo culture)
+    {
+        return DependencyProperty.UnsetValue;
+    }  
+}
+```
+
+Конвертер значений должен реализовать интерфейс **System.Windows.Data.IValueConverter**. Этот интерфейс определяет два метода: *Convert()*, который преобразует пришедшее от привязки значение в тот тип, который понимается приемником привязки, и *ConvertBack()*, который выполняет противоположную операцию.
+
+Оба метода принимают четыре параметра:
+
+* **object value**: значение, которое надо преобразовать
+
+* **Type targetType**: тип, к которому надо преобразовать значение **value**
+
+* **object parameter**: вспомогательный параметр
+
+* **CultureInfo culture**: текущая культура приложения
+
+В данном случае метод **Convert** возвращает строковое представление даты в формате "*dd.MM.yyyy*". То есть мы ожидаем, что в качестве параметра **value** будет передаваться объект **DateTime**.
+
+Метод **ConvertBack** в данном случае не имеет значения, поэтому он просто возвращает пустое значение для свойста. В другоим случае мы бы здесь получали строковое значение и преобразовывали его в **DateTime**.
+
+Теперь применим этот конвертер в xaml:
+
+```XML
+<Window.Resources>
+    <sys:DateTime x:Key="myDate">
+        2/12/2016
+    </sys:DateTime>
+    <local:DateTimeToDateConverter 
+        x:Key="myDateConverter" />
+</Window.Resources>
+<StackPanel>
+    <TextBlock 
+        Text="{Binding 
+            Source={StaticResource myDate},
+            Converter={StaticResource myDateConverter}}" />
+    <TextBlock 
+        Text="{Binding 
+            Source={StaticResource myDate}}" />
+</StackPanel>
+```
+
+Здесь искомая дата, которая выводится в текстовые блоки, задана в ресурсах. Также в ресурсах задан конвертер значений. Чтобы применить этот конвертер в конструкции привязки используется параметр **Converter** с указанием на ресурс: `Converter={StaticResource myDateConverter}`
+
+Для сравнения здесь определено два текстовых блока. Но поскольку к одному из них применяется конвертер, то отображение даты будет отличаться:
+
+![](../img/08031.png)
+
+Немного изменим код конвертера и используем передаваемый параметр:
+
+```cs
+public class DateTimeToDateConverter : IValueConverter
+{
+    public object Convert(
+        object value, 
+        Type targetType, 
+        object parameter, 
+        CultureInfo culture)
+    {
+        if(parameter!=null && parameter.ToString()=="EN")
+            return ((DateTime)value).ToString("MM-dd-yyyy");
+         
+        return ((DateTime)value).ToString("dd.MM.yyyy");
+    }
+         
+    public object ConvertBack(
+        object value, 
+        Type targetType, 
+        object parameter, 
+        CultureInfo culture)
+    {
+        return DependencyProperty.UnsetValue;
+    }
+}
+```
+
+В качестве параметра может передаваться любой объект. Если параметр в xaml не используется, то передается null. В данном случае мы проверяем, равен ли параметр строке "EN", то есть мы ожидаем, что параметр будет передавать строковое значение. И если равен, то возвращаем дату немного в другом формате.
+
+Для применения параметра изменим код xaml:
+
+```xml
+<StackPanel>
+    <TextBlock 
+        Text="{Binding 
+            Source={StaticResource myDate},
+            Converter={StaticResource myDateConverter}}" />
+    <TextBlock 
+        Text="{Binding 
+            Source={StaticResource myDate}, 
+            ConverterParameter=EN, 
+            Converter={StaticResource myDateConverter}}" />
+    <TextBlock 
+        Text="{Binding 
+            Source={StaticResource myDate}}" />
+</StackPanel>
+```
+
+Параметр привязки задается с помощью свойства **ConverterParameter**. Итак, у нас тут три текстовых блока, и применя конвертер, мы получим три разных отображения даты:
+
+![](../img/08032.png)
+
+Также мы можем использовать передаваемые в конвертер параметры культуры и типа, к которому надо преобразовать. Например, мы можем смотреть на тип целевого значения и в зависимости от результатов производить определенные действия:
+
+```cs
+public class DateTimeToDateConverter : IValueConverter
+{
+    public object Convert(
+        object value, 
+        Type targetType, 
+        object parameter, 
+        CultureInfo culture)
+    {
+        if (targetType != typeof(Brush))
+        {
+            //....
+        }
+//...................
+```
+
+В данном случае предполагается, что тип объекта, к которому надо преобразовать, представляет тип **Brush**.
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Ресурсы](./articles/wpf_resource.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Элементы управления](./t8_elements.md)

+ 1259 - 0
articles/t8_elements.md

@@ -0,0 +1,1259 @@
+[Привязка (Binding)](./t8_binding.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Каркас приложения. Модель данных. Привязка данных. Табличный вывод.](./wpf_template.md)
+
+# Элементы управления
+
+## Обзор элементов управления и их свойств
+
+Чтобы как-то взаимодействовать с пользователем, получать от пользователя ввод с клавиатуры или мыши и использовать введенные данные в программе, нам нужны элементы управления. WPF предлагает нам стандартный набор элементов управления
+
+Все элементы управления могут быть условно разделены на несколько подгрупп:
+
+* Элементы управления содержимым, например кнопки (Button), метки (Label)
+
+* Специальные контейнеры, которые содержат другие элементы, но в отличие от элементов **Grid** или **Canvas** не являются контейнерами компоновки - **ScrollViewer**, **GroupBox**
+
+* Декораторы, чьё предназначение создание определенного фона вокруг вложенных элементов, например, **Border** или **Viewbox**.
+
+* Элементы управления списками, например, **ListBox**, **ComboBox**.
+
+* Текстовые элементы управления, например, **TextBox**, **RichTextBox**.
+
+* Элементы, основанные на диапазонах значений, например, **ProgressBar**, **Slider**.
+
+* Элементы для работ с датами, например, **DatePicker** и **Calendar**.
+
+* Остальные элементы управления, которые не вошли в предыдущие подгруппы, например, **Image**.
+
+Рассмотрим некоторые из основных свойств, которые наследуются элементами управления.
+
+**Name**
+
+Наверное важнейшее свойство. По установленному имени впоследствии можно будет обращаться к элементу, как в коде, так и в xaml разметке. Например, в xaml-разметке у нас определена следующая кнопка:
+
+```xml
+<Button 
+    x:Name="button1" 
+    Width="60" 
+    Height="30" 
+    Content="Текст" 
+    Click="button1_Click" />
+```
+
+Здесь у нас задан атрибут *Click* с названием метода обработчика *button1_Click*, который будет определен в файле кода C# и будет вызываться по нажатию кнопки. Тогда в связанном файле кода C# мы можем обратиться к этой кнопке:
+
+```cs
+private void button1_Click(object sender, RoutedEventArgs e)
+{
+    button1.Content = "Привет!";
+}
+```
+
+Поскольку свойство **Name** имеет значение **button1**, то через это значение мы можем обратиться к кнопке в коде.
+
+**Visibility**
+
+Это свойство устанавливает параметры видимости элемента и может принимать одно из трех значений:
+
+* **Visible** - элемент виден и участвует в компоновке.
+* **Collapsed** - элемент не виден и не участвует в компоновке.
+* **Hidden** - элемент не виден, но при этом участвует в компоновке.
+
+Различия между **Collapsed** и **Hidden** можно продемонстрировать на примере:
+
+```xml
+<Grid>
+    <Grid.ColumnDefinitions>
+        <ColumnDefinition Width="*" />
+        <ColumnDefinition Width="*" />
+    </Grid.ColumnDefinitions>
+    <StackPanel 
+        Grid.Column="0" 
+        Background="Lavender">
+        <Button 
+            Visibility="Collapsed" 
+            Content="Панель Collapsed" />
+        <Button 
+            Height="20" 
+            Content="Visible Button" />
+    </StackPanel>
+    <StackPanel 
+        Grid.Column="1" 
+        Background="LightGreen">
+        <Button 
+            Visibility="Hidden" 
+            Content="Панель Hidden" />
+        <Button 
+            Height="20" 
+            Content="Visible Button" />
+    </StackPanel>
+</Grid>
+```
+
+![](../img/08013.png)
+
+**Свойства настройки шрифтов**
+
+* **FontFamily** - определяет семейство шрифта (например, Arial, Verdana и т.д.)
+
+* **FontSize** - определяет высоту шрифта
+
+* **FontStyle** - определяет наклон шрифта, принимает одно из трех значений - **Normal**, **Italic**, **Oblique**.
+
+* **FontWeight** - определяет толщину шрифта и принимает ряд значений, как **Black**, **Bold** и др.
+
+* **FontStretch** - определяет, как будет растягивать или сжимать текст, например, значение **Condensed** сжимает текст, а **Expanded** - растягивает.
+
+Например:
+
+```xml
+<Button 
+    Content="Hello World!" 
+    FontFamily="Verdana" 
+    FontSize="13" 
+    FontStretch="Expanded" />
+```
+
+**Cursor**
+
+Это свойство позволяет нам получить или установить курсор для элемента управления в одно из значений, например, **Hand**, **Arrow**, **Wait** и др. Например, установка курсора в коде c#: `button1.Cursor=Cursors.Hand;`
+
+**FlowDirection**
+
+Данное свойство задает направление текста. Если оно равно **RightToLeft**, то текст начинается с правого края, если - **LeftToRight**, то с левого.
+
+```xml
+<StackPanel>
+    <TextBlock FlowDirection="RightToLeft">
+        RightToLeft
+    </TextBlock>
+    <TextBlock FlowDirection="LeftToRight">
+        LeftToRight
+    </TextBlock>
+</StackPanel>
+```
+
+![](../img/08014.png)
+
+**Цвета фона и шрифта**
+
+Свойства **Background** и **Foreground** задают соответственно цвет фона и текста элемента управления.
+
+Простейший способ задания цвета в коде xaml: `Background="#ffffff"`. В качестве значения свойство **Background** (**Foreground**) может принимать запись в виде шестнадцатеричного значения в формате `#rrggbb`, где rr - красная составляющая, gg - зеленая составляющая, а bb - синяя. Также можно задать цвет в формате `#aarrggbb`.
+
+Либо можно использовать названия цветов напрямую:
+
+```xml
+<Button 
+    Width="60" 
+    Height="30" 
+    Background="LightGray" 
+    Foreground="DarkRed" 
+    Content="Цвет" />
+```
+
+## Элементы управления содержимым
+
+Элементы управления содержимым (content controls) представляют такие элементы управления, которые содержат в себе другой элемент.
+
+К элементам управления содержимым относятся такие элементы как **Button**, **Label**, **ToggleButton**, **ToolTip**, **RadioButton**, **CheckBox**, **GroupBox**, **TabItem**, **Expander**, **ScrollViewer**. Также элементом управления содержимым является и главный элемент окна - **Window**.
+
+Отличительной чертой всех этих элементов является наличие свойства **Content**, которое и устанавливает вложенный элемент. В этом элементы управления содержимым схожи с контейнерами компоновки. Только контейнеры могут иметь множество вложенных элементов, а элементы управления содержимым только один.
+
+Свойство **Content** может представлять любой объект, который может относиться к одному из двух типов:
+
+* Объект класса, не наследующего от **UIElement**. Для такого объекта вызывается метод **ToString()**, который возвращает строковое преставление объекта. Затем эта строка устанавливается в качестве содержимого.
+
+* Объект класса, наследующего от **UIElement**. Для такого объекта вызывается метод *UIElement.OnRender()*, который выполняет отрисовку внутри элемента управления содержимым.
+
+Рассмотрим на примере кнопки, которая является элементом управления содержимым:
+
+```xml
+<Button Content="Hello World!" />
+```
+
+В качестве содержимого устанавливается обычная строка. Этот же пример мы можем в XAML прописать иначе:
+
+```xml
+<Button>
+    <Button.Content>
+        Hello World!
+    </Button.Content>
+</Button>
+```
+
+Либо мы можем использовать сокращенное неявное определения свойства **Content**:
+
+```xml
+<Button>
+    Hello World!
+</Button>
+```
+
+Возьмем другой пример. Определим кнопку с именем *button1*:
+
+```xml
+<Window 
+    x:Class="ControlsApp.MainWindow"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:local="clr-namespace:ControlsApp"
+    mc:Ignorable="d"
+    Title="Элементы управления" 
+    Height="250" 
+    Width="300"
+>
+    <StackPanel>
+        <Button 
+            x:Name="button1" />
+    </StackPanel>
+</Window>
+```
+
+А в файле кода `MainWindow.xaml.cs` присвоим её свойству **Content** какой-либо объект:
+
+```cs
+using System;
+using System.Windows;
+ 
+namespace ControlsApp
+{
+    public partial class MainWindow : Window
+    {
+        public MainWindow()
+        {
+            InitializeComponent();
+            double d = 5.6;
+            button1.Content = d;
+        }
+    }
+}
+```
+
+В итоге число конвертируется в строку и устанавливается в качесте содержимого.
+
+Иначе все будет работать, если мы в качестве содержимого используем объект, унаследованный от **UIElement**:
+
+```xml
+<Button x:Name="button1">
+    <Button Content="Hello" />
+</Button>
+```
+
+Теперь в качестве содержимого будет использоваться другая кнопка, для которой при визуализации будет вызываться метод *OnRender()*:
+
+![](../img/08015.png)
+
+В отличие от контейнеров компоновки для элементов управления содержимым мы можем задать только один вложенный элемент. Если же нам надо вложить в элемент управления содержимым несколько элементов, то мы можем использовать те же контейнеры компоновки:
+
+```xml
+<Button x:Name="button1">
+    <StackPanel>
+        <TextBlock Text="Набор кнопкок" />
+        <Button 
+            Background="Red" 
+            Height="20" 
+            Content="Red" />
+        <Button 
+            Background="Yellow" 
+            Height="20" 
+            Content="Yellow" />
+        <Button 
+            Background="Green" 
+            Height="20" 
+            Content="Green" />
+    </StackPanel>
+</Button>
+```
+
+## Позиционирование контента
+
+Выравнивание содержимого внутри элемента задается свойствами *HorizontalContentAlignment* (выравнивание по горизонтали) и *VerticalContentAlignment* (выравнивание по вертикали), аналогичны свойствам *VerticalAlignment/HorizontalAlignment*. Свойство *HorizontalContentAlignment* принимает значения **Left**, **Right**, **Center** (положение по центру), **Stretch** (растяжение по всей ширине). Например:
+
+```xml
+<StackPanel>
+    <Button 
+        Margin="5" 
+        HorizontalContentAlignment="Left" 
+        Content="Left" 
+        Height="90" 
+        Width="500" />
+    <Button 
+        Margin="5" 
+        HorizontalContentAlignment="Right" 
+        Content="Right" 
+        Height="90" 
+        Width="500" />
+    <Button 
+        Margin="5" 
+        HorizontalContentAlignment="Center" 
+        Content="Center" 
+        Height="90" 
+        Width="500" />
+</StackPanel>
+```
+
+**VerticalContentAlignment** принимает значения **Top** (положение в верху), **Bottom** (положение внизу), **Center** (положение по центру), **Stretch** (растяжение по всей высоте)
+
+**Padding**
+
+С помощью свойства **Padding** мы можем установить отступ содержимого элемента:
+
+```xml
+<StackPanel>
+    <Button 
+        x:Name="button1" 
+        Padding="50 30 0 40" 
+        HorizontalContentAlignment="Left">
+        Hello World
+    </Button>
+    <Button 
+        x:Name="button2" 
+        Padding="60 20 0 30" 
+        HorizontalContentAlignment="Center">
+        Hello World
+    </Button>
+</StackPanel>
+```
+
+Свойство **Padding** задаётся в формате `Padding="отступ_слева отступ_сверху отступ_справа отступ_снизу"`.
+
+Если со всех четырех сторон предполагается один и тот же отступ, то, как и в случае с **Margin**, мы можем задать одно число:
+
+```xml
+<Button 
+    x:Name="button2" 
+    Padding="20"  
+    Content="Hello World" />
+```
+
+Важно понимать, от какой точки задается отступ. В случае с первой кнопкой в ней контект выравнивается по левому краю, поэтому отступ слева будет предполагать отступ от левого края элемента **Button**. А вторая кнопка располагается по центру. Поэтому для нее отступ слева предполагает отступ от той точки, в которой содержимое бы находилось при центрировании без применения **Padding**.
+
+Комбинация значений свойств *HorizontalContentAlignment/VerticalContentAlignment* и *Padding* позволяет оптимальным образом задать расположение содержимого.
+
+## Кнопки
+
+В WPF кнопки представлены целым рядом классов, которые наследуются от базового класса ButtonBase:
+
+![](../img/08016.png)
+
+**Button**
+
+Элемент **Button** представляет обычную кнопку:
+
+```xml
+<Button 
+    x:Name="button1" 
+    Width="60" 
+    Height="30" 
+    Background="LightGray" />
+```
+
+От класса **ButtonBase** кнопка наследует ряд событий, например, **Click**, которые позволяют обрабатывать пользовательский ввод.
+
+Чтобы связать кнопку с обработчиком события нажатия, нам надо определить в самой кнопке атрибут **Click**. А значением этого атрибута будет название обработчика в коде C#. А затем в самом коде C# определить этот обработчик.
+
+Например, код xaml:
+
+```xml
+<Button 
+    x:Name="button1" 
+    Width="60" 
+    Height="30" 
+    Content="Нажать" 
+    Click="Button_Click" />
+```
+
+И обработчик в коде C#:
+
+```cs
+private void Button_Click(object sender, RoutedEventArgs e)
+{
+    MessageBox.Show("Кнопка нажата");
+}
+```
+
+Либо можно не задавать обработчик через атрибут, а стандартным образом для C# прописать в коде: `button1.Click+=Button_Click;`
+
+К унаследованным свойствам кнопка имеет такие свойства как **IsDefault** и **IsCancel**, которые принимают значения **true** и **false**.
+
+Если свойство **IsDefault** установлено в **true**, то при нажатии клавиши Enter будет вызываться обработчик нажатия этой кнопки.
+
+Аналогично если свойство **IsCancel** будет установлено в **true**, то при нажатии на клавишу Esc будет вызываться обработчик нажатия этой кнопки.
+
+Например, определим код xaml:
+
+```xml
+<Window 
+    x:Class="ControlsApp.MainWindow"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:local="clr-namespace:ControlsApp"
+    mc:Ignorable="d"
+    Title="Элементы управления" 
+    Height="250" 
+    Width="300"
+>
+    <StackPanel>
+        <Button 
+            x:Name="acceptButton" 
+            Content="ОК" 
+            IsDefault="True" 
+            Click="acceptButton_Click" />
+        <Button 
+            x:Name="escButton" 
+            Content="Выход" 
+            IsCancel="True" 
+            Click="escButton_Click" />
+    </StackPanel>
+</Window>
+```
+
+А в коде `MainWindow.xaml.cs` определим следующий код C#:
+
+```cs
+using System.Windows;
+ 
+namespace ControlsApp
+{
+    public partial class MainWindow : Window
+    {
+        public MainWindow()
+        {
+            InitializeComponent();
+        }
+ 
+        private void acceptButton_Click(object sender, RoutedEventArgs e)
+        {
+            MessageBox.Show("Действие выполнено");
+        }
+ 
+        private void escButton_Click(object sender, RoutedEventArgs e)
+        {
+            this.Close(); // закрытие окна
+        }
+    }
+}
+```
+
+Теперь при нажатии на клавишу **Enter** будет отображаться сообщение, а при нажатии на **Esc** будет происходить выход из приложения и закрытие окна.
+
+**RepeatButton**
+
+Отличительная особенность элемента **RepeatButton** - непрерывная генерация события **Click**, пока нажата кнопка. Интервал генерации события корректируется свойствами **Delay** и **Interval**.
+
+Сам по себе элемент **RepeatButton** редко используется, однако он может служить основой для создания ползунка в элементах **ScrollBar** и **ScrollViewer**, в которых нажатие на ползунок инициирует постоянную прокрутку.
+
+**ToggleButton**
+
+Представляет элементарный переключатель. Может находиться в трех состояниях - **true**, **false** и "нулевом" (неотмеченном) состоянии, а его значение представляет значение типа **bool?** в языке C#. Состояние можно установить или получить с помощью свойства *IsChecked*. Также добавляет три события - **Checked** (переход в отмеченное состояние), **Unchecked** (снятие отметки) и **Intermediate** (если значение равно null). Чтобы отрабатывать все три события, надо установить свойство `IsThreeState="True"`
+
+**ToggleButton**, как правило, сам по себе тоже редко используется, однако при этом он служит основой для создания других более функциональных элементов, таких как **checkbox** и **radiobutton**.
+
+**CheckBox**
+
+Элемент **CheckBox** представляет собой обычный флажок. Данный элемент является производным от класса **ToggleButton** и поэтому может принимать также три состояния: **Checked**, **Unchecked** и **Intermediate**.
+
+Чтобы получить или установить определенное состояние, надо использовать свойство **IsChecked**, которое также унаследовано от **ToggleButton**:
+
+```xml
+<StackPanel x:Name="stackPanel">
+    <CheckBox 
+        x:Name="checkBox1" 
+        IsThreeState="True" 
+        IsChecked="False" 
+        Height="20" 
+        Content="Неотмечено" />
+    <CheckBox 
+        x:Name="checkBox2" 
+        IsThreeState="True" 
+        IsChecked="True" 
+        Height="20" 
+        Content="Отмечено" />
+    <CheckBox 
+        x:Name="checkBox3" 
+        IsThreeState="True" 
+        IsChecked="{x:Null}" 
+        Height="20" 
+        Content="Неопределено"/>
+</StackPanel>
+```
+
+Установка свойства `IsChecked="{x:Null}"` задает неопределенное состояние для элемента **checkbox**. Остальные два состояния задаются с помощью **True** и **False**. В данном примере также привязан к двум флажкам обработчик события **Checked**. Это событие возникает при установке **checkbox** в отмеченное состояние.
+
+А атрибут `IsThreeState="True"` указывает, что флажок может находиться в трех состояниях.
+
+![](../img/08017.png)
+
+Ключевыми событиями флажка являются события **Checked** (генерируется при установке флажка в отмеченное состояние), **Unchecked** (генерируется при снятии отметки с флажка) и **Indeterminate** (флажок переведен в неопределенное состояние). Например, определим флажок:
+
+```xml
+<CheckBox 
+    x:Name="checkBox" 
+    IsChecked="False" 
+    Height="20" 
+    Content="Флажок"
+    IsThreeState="True"
+    Unchecked="checkBox_Unchecked"
+    Indeterminate="checkBox_Indeterminate"
+    Checked="checkBox_Checked" />
+```
+
+А в файле кода C# пропишем для него обработчики:
+
+```cs
+private void checkBox_Checked(object sender, RoutedEventArgs e)
+{
+    MessageBox.Show(checkBox.Content.ToString() + " отмечен");
+}
+ 
+private void checkBox_Unchecked(object sender, RoutedEventArgs e)
+{
+    MessageBox.Show(checkBox.Content.ToString() + " не отмечен");
+}
+ 
+private void checkBox_Indeterminate(object sender, RoutedEventArgs e)
+{
+    MessageBox.Show(checkBox.Content.ToString() + " в неопределенном состоянии");
+}
+```
+
+**RadioButton**
+
+Элемент управления, также производный от **ToggleButton**, представляющий переключатель. Главная его особенность - поддержка групп. Несколько элементов **RadioButton** можно объединить в группы, и в один момент времени мы можем выбрать из этой группы только один переключатель. Например,
+
+```xml
+<StackPanel x:Name="stackPanel">
+    <RadioButton 
+        GroupName="Languages" 
+        Content="C#" 
+        IsChecked="True" />
+    <RadioButton 
+        GroupName="Languages" 
+        Content="VB.NET" />
+    <RadioButton 
+        GroupName="Languages" 
+        Content="C++" />
+    <RadioButton 
+        GroupName="Technologies" 
+        Content="WPF" 
+        IsChecked="True" />
+    <RadioButton 
+        GroupName="Technologies" 
+        Content="WinForms" />
+    <RadioButton 
+        GroupName="Technologies" 
+        Content="ASP.NET" />
+</StackPanel>
+```
+
+![](../img/08018.png)
+
+Чтобы включить элемент в определенную группу, используется свойство **GroupName**. В данном случае у нас две группы - **Languages** и **Technologies**. Мы можем отметить не более одного элемента **RadioButton** в пределах одной группы, зафиксировав тем самым выбор из нескольких возможностей.
+
+Чтобы проследить за выбором того или иного элемента, мы также можем определить у элементов событие **Checked** и его обрабатывать в коде:
+
+```xml
+<RadioButton 
+    GroupName="Languages" 
+    Content="VB.NET" 
+    Checked="RadioButton_Checked" />
+```
+
+Обработчик в файле кода:
+
+```cs
+private void RadioButton_Checked(object sender, RoutedEventArgs e)
+{
+    RadioButton pressed = (RadioButton)sender;
+    MessageBox.Show(pressed.Content.ToString());
+}
+```
+
+## Текстовые элементы управления
+
+**TextBox**
+
+Этот элемент представляет поле для ввода текстовой информации.
+
+Он имеет свойства **TextWrapping**, **TextAlignment** и **TextDecorations**.
+
+* Свойство **TextWrapping** позволяет переносить текст при установке этого свойства `TextWrapping="Wrap"`. По умолчанию это свойство имеет значение **NoWrap**, поэтому текст не переносится.
+
+* Свойство **TextAlignment** выравнивает текст по центру (значение **Center**), правому (**Right**) или левому краю (**Left**): `<TextBlock TextAlignment="Right">`
+
+* Для декорации текста используется свойство **TextDecorations**, например, если `TextDecorations="Underline"`, то текст будет подчеркнут.
+
+* С помощью свойства **MaxLength** можно задать предельное количество вводимых символов.
+
+```xml
+<TextBox 
+    MaxLength="250" 
+    TextChanged="TextBox_TextChanged">
+    Начальный текст
+</TextBox>
+```
+
+В коде C# мы можем обработать событие изменения текста:
+
+```cs
+private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
+{
+    TextBox textBox = (TextBox)sender;
+    MessageBox.Show(textBox.Text);
+}
+```
+
+По умолчанию, если вводимый текст превышает установленные границы поля, то текстовое поле растет, чтобы вместить весь текст. Но визуально это не очень хорошо выглядит. Поэтомуo мы можем перенести непомещающийся текст на новую строку, установив свойство `TextWrapping="Wrap"`.
+
+Чобы переводить по нажатию на клавишу **Enter** курсор на следующую строку, нам надо установить свойство `AcceptsReturn="True"`.
+
+Также мы можем добавить полю возможность создавать табуляцию с помощью клавиши **Tab**, установив свойство `AcceptsTab="True"`
+
+Если нам вдруг потребуется перенести текст на другую строку, то тогда мы можем использовать элемент **LineBreak**:
+
+```xml
+<TextBox>
+    Однажды в студеную зимнюю пору
+    <LineBreak />
+    Я из лесу вышел
+</TextBox>
+```
+
+Для отображения полос прокрутки TextBox поддерживает свойства **VerticalScrollBarVisibility** и **НоrizontalScrollBarVisibility**:
+
+```xml
+<TextBox 
+    AcceptsReturn="True" 
+    Height="100" 
+    VerticalScrollBarVisibility="Auto"
+    HorizontalScrollBarVisibility="Auto">
+    Начальный текст
+</TextBox>
+```
+
+Возможно, при создании приложения нам потребуется сделать текстовое поле недоступным для ввода (на время в зависимости от условий или вообще), тогда для этого нам надо установить свойство `IsReadOnly="True"`.
+
+Для выделения текста есть свойства **SelectionStart**, **SelectionLength** и **SelectionText**. Например, выделим программно текст по нажатию кнопки:
+
+```xml
+<StackPanel>
+    <TextBox 
+        x:Name="textBox1" 
+        Height="100" SelectionBrush="Blue" />
+    <Button 
+        Content="Выделить текст" 
+        Height="30" 
+        Width="100" 
+        Click="Button_Click" 
+        Margin="10" />
+</StackPanel>
+```
+
+Обработчик нажатия кнопки:
+
+```cs
+private void Button_Click(object sender, RoutedEventArgs e)
+{
+    textBox1.SelectionStart = 5;
+    textBox1.SelectionLength = 10;
+    textBox1.Focus();
+    // данное выражение эквивалентно
+    //textBox1.Select(5, 10);
+}
+```
+
+**Проверка орфографии**
+
+**TextBox** обладает встроенной поддержкой орфографии. Чтобы ее задействовать, надо установить свойство `SpellCheck.IsEnabled="True"`. Кроме того, по умолчанию проверка орфографии распространяется только на английский язык, поэтому, если приложение заточено под другой язык, нам надо его явным образом указать через свойство **Language**:
+
+```xml
+<DockPanel>
+    <TextBox 
+        SpellCheck.IsEnabled="True" 
+        Language="ru-ru">
+        Привет, как дила?
+    </TextBox>
+</DockPanel>
+```
+
+**Метка (Label)**
+
+Главной особенностью меток является поддержка мнемонических команд-клавиш быстрого доступа, которые передают фокус связанному элементу. Например,
+
+```xml
+<Label 
+    Target="{Binding ElementName=TextBox1}">
+    _привет
+</Label>
+<TextBox 
+    Name="TextBox1" 
+    Margin="0 30 0 0" 
+    Height="30" 
+    Width="100" />
+```
+
+Теперь, нажав на клавишу "п", мы переведем фокус на связанное текстовое поле. При вызове приложения подчеркивание не отображается, чтобы отображать подчеркивание, надо нажать на клавишу **Alt**. Тогда чтобы перевести фокус на связанное текстовое поле необходимо будет нажать сочетание `Alt + "п"`.
+
+**PasswordBox**
+
+Элемент предназначен для ввода парольной информации. По сути это тоже текстовое поле, только для ввода символов используется маска. Свойство **PasswordChar** устанавливает символ маски, отображаемый при вводе пароля. Если это свойство не задано, то по умолчанию для маски символа используется черная точка. Свойство **Password** устанавливает парольную строку, отображаемую по умолчанию при загрузке окна приложения.
+
+```xml
+<StackPanel>
+    <PasswordBox 
+        PasswordChar="*" 
+        MinHeight="30" />
+    <PasswordBox 
+        MinHeight="30" />
+</StackPanel>
+```
+
+![](../img/08019.png)
+
+**RichTextBox**
+
+Для вывода текстового содержимого, насыщенного форматированием, графикой, предназначен **RichTextBox**. Можно даже сказать, что он выводит не просто текст, а документы с более сложным форматированием, чем обычный **TextBox**. Более подробно о нем, а также как на его основе создать простой текстовый редактор, мы поговорим в лекции, посвященной документам.
+
+## Элементы управления списками
+
+Эти элементы представлены в WPF довольно широко. Все они являются производными от класса ItemsControl. Все они содержат коллекцию элементов. Элементы могут быть напрямую добавлены в коллекцию, возможна также привязка некоторого массива данных к коллекции.
+
+Возьмем простейший элемент-список - **ListBox**:
+
+```xml
+<Window 
+    x:Class="ControlsApp.MainWindow"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:local="clr-namespace:ControlsApp"
+    xmlns:sys="clr-namespace:System;assembly=mscorlib"
+    mc:Ignorable="d"
+    Title="ListBox" 
+    Height="200" 
+    Width="300"
+>
+    <Grid>
+        <ListBox Name="list">
+            <sys:String>Lumia 950</sys:String>
+            <sys:String>iPhone 6S Plus</sys:String>
+            <sys:String>Xiaomi Mi5</sys:String>
+            <sys:String>Nexus 5X</sys:String>
+        </ListBox>
+    </Grid>
+</Window>
+```
+
+Все элементы, размещенные внутри спискового элемента **ListBox**, представляют элементы списка.
+
+Коллекция объектов внутри элемента-списка доступна в виде свойства **Items**. Для управления элементами из этой коллекции мы можем использовать следующие методы:
+
+* **Add(object item)**: добавление элемента
+* **Clear()**: полная очистка коллекции
+* **Insert(int index, object item)**: вставка элемента по определенному индексу в коллекции
+* **Remove(object item)**: удаление элемента
+* **RemoveAt(int index)**: удаление элемента по индексу
+
+А свойство **Count** позволяет узнать, сколько элементов в коллекции.
+
+Например, применительно к вышеопределенному списку мы бы могли написать в коде C#:
+
+```cs
+list.Items.Add("LG G5");
+list.Items.RemoveAt(1); // удаляем второй элемент
+```
+
+Нам необязательно вручную заполнять значения элемента управления списком, так как мы можем установить свойство **ItemsSource**, задав в качестве параметра коллекцию, из которой будет формироваться элемент управления списком. Например, в коде xaml-разметки определим пустой список:
+
+```xml
+<Grid>
+    <ListBox Name="list" />
+</Grid>
+```
+
+А в файле отделенного кода выполним наполнение списка:
+
+```cs
+public partial class MainWindow : Window
+{
+    public MainWindow()
+    {
+        InitializeComponent();
+ 
+        string[] phones = { "iPhone 6S", "Lumia 950", "Nexus 5X", "LG G4", "Xiaomi MI5", "HTC A9" };
+        list.ItemsSource = phones;
+    }
+}
+```
+
+Свойство **ItemsSource** в качестве значения принимает массив, хотя это моет быть и список типа **List**. И каждый элемент этого массива переходит в **ListBox**.
+
+Еще одно важное свойство списковых элементов - это свойство **DisplayMemberPath**. Оно позволяет выбирать для отображения элементов значение одного из свойств объекта. Например, создадим в коде новый класс **Phone**:
+
+```cs
+class Phone
+{
+    public string Title { get; set; }
+    public string Company { get; set; }
+    public int Price { get; set; }
+}
+```
+
+Теперь создадим в xaml набор объектов этого класса **Phone** и выведем в списке значение свойства **Title** этих объектов:
+
+```xml
+<Window 
+    x:Class="ControlsApp.MainWindow"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:local="clr-namespace:ControlsApp"
+    mc:Ignorable="d"
+    Title="ListBox" 
+    Height="220" 
+    Width="300"
+>
+    <Grid Background="Lavender">
+        <ListBox 
+            Name="list" 
+            DisplayMemberPath="Title">
+            <local:Phone 
+                Title="iPhone 6S" 
+                Company="Apple" 
+                Price="54990" />
+            <local:Phone 
+                Title="Lumia 950" 
+                Company="Microsoft" 
+                Price="39990" />
+            <local:Phone 
+                Title="Nexus 5X" 
+                Company="Google" 
+                Price="29990" />
+        </ListBox>
+    </Grid>
+</Window>
+```
+
+Поскольку мы используем класс, определенный в текущем проекте, то соответственно у нас обязательно должно быть подключено пространство имен проекте: `xmlns:local="clr-namespace:ControlsApp"`. В принципе по умолчанию WPF уже его подключает. Кроме того, чтобы не возникало проблем с разметкой XAML, желательно сделать перестроение проекта. И в итоге окно нам выведет только названия смартфонов:
+
+![](../img/08020.png)
+
+
+То же самое мы бы могли сделать программным способом:
+
+```cs
+list.ItemsSource = new List<Phone>
+{
+    new Phone { Title="iPhone 6S", Company="Apple", Price=54990 },
+    new Phone {Title="Lumia 950", Company="Microsoft", Price=39990 },
+    new Phone {Title="Nexus 5X", Company="Google", Price=29990 }
+};
+list.DisplayMemberPath = "Title";
+```
+
+Все элементы управления списками поддерживают выделение входящих элементов. Выделенный элемент(ы) можно получить с помощью свойств **SelectedItem**(**SelectedItems**), а получить индекс выделенного элемента - с помощью свойства **SelectedIndex**. Свойство **SelectedValue** позволяет получить значение выделенного элемента.
+
+При выделении элемента в списке генерируется событие **SelectionChanged**, которое мы можем обработать. Например, возьмем предыдущий список:
+
+```xml
+<ListBox 
+    Name="list" 
+    DisplayMemberPath="Title" 
+    SelectionChanged="list_Selected">
+    <local:Phone 
+        Title="iPhone 6S" 
+        Company="Apple" 
+        Price="54990" />
+    <local:Phone 
+        Title="Lumia 950" 
+        Company="Microsoft" 
+        Price="39990" />
+    <local:Phone 
+        Title="Nexus 5X" 
+        Company="Google" 
+        Price="29990" />
+</ListBox>
+```
+
+А в файле кода определим обработчик для этого события:
+
+```cs
+private void list_Selected(object sender, RoutedEventArgs e)
+{
+    Phone p = (Phone)list.SelectedItem;
+    MessageBox.Show(p.Title);
+}
+```
+
+Важно учитывать, что так как в разметке xaml в списке определены элементы **Phone**, то в коде мы можем привести объект *list.SelectedItem* к типу **Phone**.
+
+**ComboBox**
+
+**ComboBox** содержит коллекцию элементов и образует выпадающий список:
+
+```xml
+<ComboBox 
+    Name="phonesList" 
+    Height="30" 
+    VerticalAlignment="Top">
+    <TextBlock>LG Nexus 5X</TextBlock>
+    <TextBlock>Huawai Nexus 6P</TextBlock>
+    <TextBlock>iPhone 6S</TextBlock>
+    <TextBlock>iPhone 6S Plus</TextBlock>
+    <TextBlock>Microsoft Lumia 950</TextBlock>
+</ComboBox>
+```
+
+![](../img/08021.png)
+
+**ComboBoxItem**
+
+В качестве элементов в **ComboBox**-e мы можем использовать различные компоненты, но наиболее эффективным является применение элемента **ComboBoxItem**. **ComboBoxItem** представляет элемент управления содержимым, в который через свойство **Content** мы можем поместить другие элементы. Например:
+
+```xml
+<ComboBox 
+    Height="50" 
+    Width="150" 
+    VerticalAlignment="Top"
+>
+    <ComboBoxItem IsSelected="True">
+        <StackPanel Orientation="Horizontal">
+            <Image 
+                Source="cats.jpg"  
+                Width="60" />
+            <TextBlock>cats.jpg</TextBlock>
+        </StackPanel>
+    </ComboBoxItem>
+    <StackPanel Orientation="Horizontal">
+        <Image 
+            Source="windowcat.jpg" 
+            Width="60" />
+        <TextBlock>windowcat.jpg</TextBlock>
+    </StackPanel>
+    <StackPanel Orientation="Horizontal">
+        <Image 
+            Source="234.jpg" 
+            Width="60" />
+        <TextBlock>234.jpg</TextBlock>
+    </StackPanel>
+</ComboBox>
+```
+
+![](../img/08022.png)
+
+Для создания первого элемента использовался элемент **ComboBoxItem**. Для второго и третьего такие элементы создаются неявно. Однако использование **ComboBoxItem** имеет преимущество, так как мы можем выделить данный элемент, установив свойство `IsSelected="True"`, либо можем сделать недоступным с помощью установки свойства `IsEnabled="False"`.
+
+**Событие SelectionChanged**
+
+Обрабатывая событие **SelectionChanged**, мы можем динамически получать выделенный элемент:
+
+```xml
+<ComboBox 
+    Height="25" 
+    Width="150" 
+    SelectionChanged="ComboBox_Selected"
+>
+<!-- остальное содержимое списка-->
+</ComboBox>
+```
+
+Обработка события в коде C#:
+
+```cs
+private void ComboBox_Selected(object sender, RoutedEventArgs e)
+{
+    ComboBox comboBox = (ComboBox)sender;
+    ComboBoxItem selectedItem = (ComboBoxItem)comboBox.SelectedItem;
+    MessageBox.Show(selectedItem.Content.ToString());
+}
+```
+
+Правда, для элементов со сложным содержимым подобный способ может не пройти, и если мы захотим получить текст, до него придется добираться, спускаясь по дереву вложенных элементов.
+
+**Свойства**
+
+Установка свойства `IsEditable="True"` позволяет вводить в поле списка начальные символы, а затем функция автозаполнения подставит подходящий результат. По умолчанию свойство имеет значение **False**.
+
+Это свойство работает в комбинации со свойством **IsReadOnly**: оно указывает, является поле ввода доступным только для чтения. По умолчанию имеет значение **False**, поэтому если `IsEditable="True"`, то мы можем вводить туда произвольный текст.
+
+Еще одно свойство **StaysOpenOnEdit** при установке в **True** позволяет сделать список раскрытым на время ввода значений в поле ввода.
+
+## DataGrid
+
+**DataGrid** во многом похож на **ListView**, но более сложный по характеру и допускает редактирование содержимого таблицы.
+
+В разделе о **ListView** мы создали класс Phone, объекты которого выводили в список:
+
+
+```cs
+public class Phone
+{
+    public string Title { get; set; }
+    public string Company { get; set; }
+    public int Price { get; set; }
+}
+```
+
+Теперь же выведем объекты в таблицу **DataGrid**. Чтобы **DataGrid** автоматически разбивал таблицу на столбцы, установим свойство `AutoGenerateColumns="True"` (вроде можно и не устанавливать - оно по-умолчанию включено):
+
+```xml
+<Window 
+    x:Class="ControlsApp.MainWindow"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:local="clr-namespace:ControlsApp"
+    xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
+        
+    mc:Ignorable="d"
+    Title="DataGrid" 
+    Height="220" 
+    Width="300">
+    <Grid Background="Lavender">
+        <DataGrid 
+            x:Name="phonesGrid" 
+            AutoGenerateColumns="True" 
+            ItemsSource="{DynamicResource 
+                ResourceKey=phones}">
+            <DataGrid.Resources>
+                <col:ArrayList x:Key="phones">
+                    <local:Phone 
+                        Title="iPhone 6S" 
+                        Company="Apple" 
+                        Price="54990" />
+                    <local:Phone 
+                        Title="Lumia 950" 
+                        Company="Microsoft" 
+                        Price="39990" />
+                    <local:Phone 
+                        Title="Nexus 5X" 
+                        Company="Google" 
+                        Price="29990" />
+                </col:ArrayList>
+            </DataGrid.Resources>
+        </DataGrid>
+    </Grid>
+</Window>
+```
+
+В данном случае префикс **local** ссылается на пространство имен текущего проекта, в котором определен класс **Phone** (`xmlns:local="clr-namespace:Controls"`), а **col** - префикс-ссылка на пространство имен **System.Collections** (`xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"`). И это даст в итоге следующий вывод:
+
+![](../img/08023.png)
+
+Программная установка источника для **DataGrid**:
+
+```cs
+List<Phone> phonesList = new List<Phone>
+{
+    new Phone { Title="iPhone 6S", Company="Apple", Price=54990 },
+    new Phone {Title="Lumia 950", Company="Microsoft", Price=39990 },
+    new Phone {Title="Nexus 5X", Company="Google", Price=29990 }
+};
+phonesGrid.ItemsSource = phonesList;
+```
+
+**Некоторые полезные свойства *DataGrid***
+
+&nbsp;|&nbsp;
+--|-- 
+RowBackground и AlternatingRowBackground | Устанавливают фон строки. Если установлены оба свойства, цветовой фон чередуется: RowBackground - для нечетных строк и AlternatingRowBackground - для четных
+ColumnHeaderHeight | Устанавливает высоту строки названий столбцов.
+ColumnWidth | Устанавливает ширину столбцов.
+RowHeight | Устанавливает высоту строк.
+GridLinesVisibility | Устанавливает видимость линий, разделяющих столбцы и строки. Имеет четыре значения - All - видны все линии, Horizontal - видны только горизонтальные линии, Vertical - видны только вертикальные линии, None - линии отсутствуют
+HeadersVisibility | Задает видимость заголовков
+HorizontalGridLinesBrush и VerticalGridLinesBrush | Задает цвет горизонтальных и вертикальных линий соответственно
+
+Хотя предыдущий пример довольно прост, в нем есть несколько недочетов. Во-первых, у нас нет возможности повлиять на расстановку столбцов. Во-вторых, заголовки определены по названиям свойств, которые на английском языке, а хотелось бы на русском. В этом случае мы должны определить свойства отображения столбцов сами. Для этого надо воспользоваться свойством *DataGrid.Columns* и определить коллекцию столбцов для отображения в таблице.
+
+Причем можно задать также и другой тип столбца, отличный от текстового. DataGrid поддерживает следующие варианты столбцов:
+
+&nbsp;|&nbsp;
+--|-- 
+DataGridTextColumn | Отображает элемент TextBlock или TextBox при редактировании
+DataGridHyperlinkColumn | Представляет гиперссылку и позволяет переходить по указанному адресу
+DataGridCheckBoxColumn | Отображает элемент CheckBox
+DataGridComboBoxColumn | Отображает выпадающий список - элемент ComboBox
+DataGridTemplateColumn | Позволяет задать специфичный шаблон для отображения столбца
+
+Перепишем предыдущий пример с учетом новой информации:
+
+```xml
+<DataGrid 
+    x:Name="phonesGrid" 
+    AutoGenerateColumns="False" 
+    HorizontalGridLinesBrush="DarkGray"
+    RowBackground="LightGray" 
+    AlternatingRowBackground="White">
+            
+    <DataGrid.Items>
+        <local:Phone 
+            Title="iPhone 6S" 
+            Company="Apple" 
+            Price="54990" />
+        <local:Phone 
+            Title="Lumia 950" 
+            Company="Microsoft" 
+            Price="39990" />
+        <local:Phone 
+            Title="Nexus 5X" 
+            Company="Google" 
+            Price="29990" />
+    </DataGrid.Items>
+    <DataGrid.Columns>
+        <DataGridTextColumn 
+            Header="Модель" 
+            Binding="{Binding Path=Title}" 
+            Width="90" />
+        <DataGridHyperlinkColumn 
+            Header="Компания" 
+            Binding="{Binding Path=Company}" 
+            Width="80" />
+        <DataGridTextColumn 
+            Header="Цена" 
+            Binding="{Binding Path=Price}" 
+            Width="50" />
+    </DataGrid.Columns>
+</DataGrid>
+```
+
+![](../img/08024.png)
+
+Среди свойств **DataGrid** одним из самых интересных является *RowDetailsTemplate*. Оно позволяет задать шаблон отображения дополнительной информации касательно данной строки. Изменим элемент **DataGrid**:
+
+```xml
+<DataGrid 
+    x:Name="phonesGrid" 
+    AutoGenerateColumns="False" 
+    HorizontalGridLinesBrush="DarkGray"
+    RowBackground="LightGray" 
+    AlternatingRowBackground="White">
+            
+    <DataGrid.Items>
+        <local:Phone 
+            Title="iPhone 6S" 
+            Company="Apple" 
+            Price="54990" />
+        <local:Phone 
+            Title="Lumia 950" 
+            Company="Microsoft" 
+            Price="39990" />
+        <local:Phone 
+            Title="Nexus 5X" 
+            Company="Google" 
+            Price="29990" />
+    </DataGrid.Items>
+    <DataGrid.Columns>
+        <DataGridTextColumn 
+            Header="Модель" 
+            Binding="{Binding Path=Title}" 
+            Width="90" />
+        <DataGridHyperlinkColumn 
+            Header="Компания" 
+            Binding="{Binding Path=Company}" 
+            Width="80" />
+        <DataGridTextColumn 
+            Header="Цена" 
+            Binding="{Binding Path=Price}" 
+            Width="50" />
+    </DataGrid.Columns>
+     
+    <DataGrid.RowDetailsTemplate>
+        <DataTemplate>
+            <StackPanel Orientation="Horizontal">
+                <TextBlock 
+                    Text="{Binding Path=Price}" />
+                <TextBlock 
+                    Text=" рублей по скидке" />
+            </StackPanel>
+        </DataTemplate>
+    </DataGrid.RowDetailsTemplate>
+     
+</DataGrid>
+```
+
+![](../img/08025.png)
+
+## Работа с изображениями. Image и InkCanvas
+
+**Элемент Image**
+
+Элемент **Image** предназначен для работы с изображениями. Свойство *Source* позволяет задать путь к изображению, например:
+
+
+```xml
+<Image Source="myPhoto.jpg" />
+```
+
+WPF поддерживает различны форматы изображений: .bmp, .png, .gif, .jpg и т.д.
+
+Также элемент позволяет проводить некоторые простейшие транформации с изображениями. Например, с помощью объекта *FormatConvertedBitmap* и его свойства *DestinationFormat* можно получить новое изображение:
+
+```xml
+<Grid Background="Black">
+    <Grid.ColumnDefinitions>
+        <ColumnDefinition Width="2.5*" />
+        <ColumnDefinition Width="*" />
+    </Grid.ColumnDefinitions>
+    <Image Grid.Column="0" x:Name="mainImage">
+        <Image.Source>
+            <FormatConvertedBitmap Source="3.jpg"
+                DestinationFormat="Gray32Float" />
+        </Image.Source>
+    </Image>
+    <StackPanel Grid.Column="1">
+        <Image Source="1.jpg" />
+        <Image Source="2.jpg" />
+        <Image Source="4.jpg" />
+        <Image Source="3.jpg" />
+    </StackPanel>
+</Grid>
+```
+
+![](../img/08026.png)
+
+**InkCanvas**
+
+**InkCanvas** представляет собой полотно, на котором можно рисовать. Первоначально оно предназначалось для стилуса, но в WPF есть поддержка также и для мыши для обычных ПК. Его очень просто использовать:
+
+```xml
+<InkCanvas Background="LightCyan" />
+```
+
+Либо мы можем вложить в InkCanvas какое-нибудь изображение и на нем уже рисовать:
+
+```xml
+<InkCanvas>
+    <Image 
+        Source="2.jpg"  
+        Width="300" 
+        Height="250"  />
+</InkCanvas>
+```
+
+![](../img/08027.png)
+
+Все рисование в итоге представляется в виде штрихов - элементов класса **System.Windows.Ink.Stroke** и хранится в коллекции **Strokes**, определенной в классе **InkCanvas**.
+
+**Режим рисования**
+
+**InkCanvas** имеет несколько режимов, они задаются с помощью свойства **EditingMode**, значения для которого берутся из перечисления **InkCanvasEditingMode**.. Эти значения бывают следующими:
+
+* **Ink**: используется по умолчанию и предполагает рисование стилусом или мышью
+* **InkAndGesture**: рисование с помощью мыши/стилуса, а также с помощью жестов (Up, Down, Tap и др.)
+* **GestureOnly**: рисование только с помощью жестов пользователя
+* **EraseByStroke**: стирание всего штриха стилусом
+* **EraseByPoint**: стирание только части штриха, к которой прикоснулся стилус
+* **Select**: выделение всех штрихов при касании
+* **None**: отсутствие какого-либо действия
+
+Используя эти значения и обрабатывая события **InkCanvas**, такие как **StrokeCollected** (штрих нарисован), **StrokeErased** (штрих стерли) и др., можно управлять набором штрихов и создавать более функциональные приложения на основе **InkCanvas**.
+
+[Привязка (Binding)](./t8_binding.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Каркас приложения. Модель данных. Привязка данных. Табличный вывод.](./wpf_template.md)

+ 875 - 0
articles/t8_win_app.md

@@ -0,0 +1,875 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Библиотеки классов](./t7_dll.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Ресурсы](./wpf_resource.md)
+
+# Обзор типов оконных приложений в C#. Знакомство со структурой проекта WPF/Avalonia. Компоновка. Image. Ресурсы.
+
+>Содрано [отсюда](https://metanit.com/sharp/wpf/1.php)
+
+* [Технологии создания оконных приложений](#технологии-создания-оконных-приложений)
+* [Особенности платформ WPF/Avalonia](#особенности-платформ-wpfavalonia)
+* [Установка Avalonia](#установка-avalonia)
+* [Создание оконного приложения](#создание-оконного-приложения)
+* [Структура проекта](#структура-проекта)
+* [Компоновка](#компоновка)
+* [Image. Ресурсы](#image-ресурсы)
+
+
+## Технологии создания оконных приложений
+
+В C# есть несколько технологий для созданий оконных приложений:
+
+* **Windows Forms** - разработка "классических" приложений Windows, считается устаревшей
+
+    **Windows Forms** — интерфейс программирования приложений (API), отвечающий за графический интерфейс пользователя и являющийся частью *Microsoft .NET Framework*. Данный интерфейс упрощает доступ к элементам интерфейса Microsoft Windows за счет создания обёртки для существующего *Win32 API* в управляемом коде. Причём управляемый код — классы, реализующие API для **Windows Forms**, не зависят от языка разработки.
+
+* WPF (Window Presentation Foundation) - более современный фреймворк для .NET Framework, но заточен только под Windows (отрисовку реализует через **DirectX**)
+
+    >Стоит отметить, что *.NET Framework* считается устаревшей технологией и на смену ей пришла кроссплатформенная библиотека *.NET Core*. Для неё есть аналог **WPF** - **Avalonia**, совместимый, на уровне разметки, фреймворк, работающий на **OpenGL**.
+
+* UWP (Universal Windows Platform) - вроде как "последний писк", рассчитанный на разработку универсальных приложений под Windows Phone, Windows 8 и.т.д
+
+**Avalonia** являеся практически калькой с **WPF**, поэтому далее я их рассматриваю одновременно.
+
+## Особенности платформ WPF/Avalonia
+
+Если при создании традиционных приложений на основе **Windows Forms** за отрисовку элементов управления и графики отвечали такие части ОС Windows, как **User32** и **GDI+**, то приложения **WPF** основаны на **DirectX** (**Avalonia**, соответственно, на **OpenGL**). В этом состоит ключевая особенность рендеринга графики: используя **WPF/Avalonia**, значительная часть работы по отрисовке графики, как простейших кнопочек, так и сложных 3D-моделей, ложиться на графический процессор на видеокарте, что также позволяет воспользоваться аппаратным ускорением графики.
+
+Одной из важных особенностей является использование языка декларативной разметки интерфейса XAML, основанного на XML: вы можете создавать насыщенный графический интерфейс, используя или декларативное объявление интерфейса, или код C#, либо совмещать и то, и другое.
+
+### Преимущества WPF/Avalonia
+
+Что вам, как разработчику, предлагает **WPF/Avalonia**?
+
+* Использование традиционных языков .NET-платформы - C# для создания логики приложения
+
+* Возможность декларативного определения графического интерфейса с помощью специального языка разметки XAML, основанном на XML и представляющем альтернативу программному созданию графики и элементов управления, а также возможность комбинировать XAML и C#
+
+* Независимость от разрешения экрана: поскольку в WPF/Avalonia все элементы измеряются в независимых от устройства единицах, приложения на WPF/Avalonia легко масштабируются под разные экраны с разным разрешением.
+
+* Новые возможности, которых сложно было достичь в **Windows Forms**, например, создание трехмерных моделей, привязка данных, использование таких элементов, как стили, шаблоны, темы и др.
+
+* Хорошее взаимодействие с **Windows Forms** (только **WPF**), благодаря чему, например, в приложениях **WPF** можно использовать традиционные элементы управления из **Windows Forms**.
+
+* Богатые возможности по созданию различных приложений: это и мультимедиа, и двухмерная и трехмерная графика, и богатый набор встроенных элементов управления, а также возможность самим создавать новые элементы, создание анимаций, привязка данных, стили, шаблоны, темы и многое другое
+
+* Аппаратное ускорение графики - вне зависимости от того, работаете ли вы с 2D или 3D, графикой или текстом, все компоненты приложения транслируются в объекты, понятные Direct3D/OpenGL, и затем визуализируются с помощью процессора на видеокарте, что повышает производительность, делает графику более плавной.
+
+## Установка Avalonia
+
+Далее мы будем использовать Avalonia, но так как он не входит в .NET, то нужно его установить:
+
+1. В основном окне **Rider** выбрать вкладку *Configure -> Plugins*:
+
+    ![](../img/rider12.png)
+
+    И установите `AvaloniaRider` (в строке поиска введите "avalonia")
+
+    При установке может выдать сообщение, что плагин разработан не в **JetBrains** и использовать на свой страх - соглашаемся (**Accept**)
+
+1. После установки плагина перезагрузите IDE и установите шаблоны проектов для **Avalonia**. В консоли выполните команду: 
+
+    ```
+    dotnet new install Avalonia.Templates
+    ```
+
+    >Для .NET 6.0 и более ранних версий замените install на --install.
+    >Версию .NET можно узнать выполнив в консоли команду `dotnet --version`
+
+## Создание оконного приложения
+
+Запустите **Rider** и создайте новое решение:
+
+Если шаблоны установлены нормально, то в окне создания нового проекта появится секция *Other*:
+
+Выберите **Avalonia .NET Core App** 
+
+При создании задаете *Название решения*, *Имя проекта* и, если нужно, *Расположение*. Остальные параметры оставляем по-умолчанию.
+
+>Название проекта должно отражать предметную область или название компании (за это есть отдельные баллы на чемпионате и демо-экзамене)
+
+**Основные типы файлов проекта:**
+
+* **.AXAML** (Avalonia eXtended Application Markup Languale) - язык разметки, очень похож на XML. В таких файлах хранится описание внешнего вида окна. 
+* **.cs** - файлы с исходным кодом на C# для окна.
+
+## Структура проекта
+
+В структуре проекта следует выделить следующие моменты:
+
+**Во-первых**, в проекте имеется файл `App.axaml` и связанный с ним файл кода `App.axaml.cs` - это глобальные файлы для всего приложения, позже мы о них поговорим подробнее. А пока только следует знать, что в `App.axaml.cs` задается класс главного окна программы, которое будет открываться при запуске приложения. Если вы откроете этот файл, то можете найти в нем строку `desktop.MainWindow = new MainWindow();` - то есть в данном случае, когда мы запустим приложение, главным будет окно из класса `MainWindow`.
+
+Далее в структуре определены файл разметки `MainWindow.axaml` и файл связанного кода `MainWindow.axaml.cs`. Файл `MainWindow.axaml` и представляет определение окна приложения, которое мы увидим при запуске.
+
+### Введение в язык XAML
+
+**AXAML (Avalonia eXtensible Application Markup Language)** - язык разметки, используемый для инициализации объектов в технологиях на платформе .NET. Применительно к **Avalonia** данный язык используется прежде всего для создания пользовательского интерфейса декларативным путем, наподобие HTML в веб-программировании.
+
+**AXAML** - не является обязательной частью приложения, мы вобще можем обходиться без него, создавая все элементы в файле связанного с ним кода на языке C#. Однако использование AXAML все-таки несет некоторые преимущества:
+
+* Возможность отделить графический интерфейс от логики приложения, благодаря чему над разными частями приложения могут относительно автономно работать разные специалисты: над интерфейсом - дизайнеры, над кодом логики - программисты.
+
+* Компактность, понятность, код на AXAML относительно легко поддерживать.
+
+При компиляции приложения код в axaml-файлах также компилируется в бинарное представление кода axaml. И затем это бинарное представление встраивается в финальную сборку приложения - exe или dll-файл.
+
+#### Структура и пространства имен AXAML
+
+При создании нового проекта он уже содержит файлы с кодом axaml. Так, создаваемый по умолчанию в проекте файл `MainWindow.axaml` будет иметь следующую разметку:
+
+```xml
+<Window 
+    xmlns="https://github.com/avaloniaui"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    mc:Ignorable="d" 
+    d:DesignWidth="800" 
+    d:DesignHeight="450"
+    x:Class="AvaloniaFirst.MainWindow"
+    Title="AvaloniaFirst"
+>
+    Welcome to Avalonia!
+</Window>
+```
+
+Если вы совершенно не знакомы с axaml и с xml, то даже этот небольшой минимальный код окна может вызывать затруднения.
+
+Подобно структуре веб-страничке на html, здесь есть некоторая иерархия элементов. Элементом верхнего уровня является тег **Window**, который представляет собой окно приложения. При создании других окон в приложении нам придется всегда начинать объявление интерфейса с элемента **Window**, поскольку это элемент самого верхнего уровня.
+
+Кроме **Window** существует еще два элемента верхнего уровня:
+
+* Page
+* Application
+
+Элемент **Window** имеет вложенный текст (Welcome to Avalonia!), а также подобно html-элементам ряд атрибутов (*Title*, *DesignWidth*, *DesignHeight*) - они задают заголовок, ширину и высоту окна соответственно.
+
+#### Пространства имен AXAML
+
+При создании кода на языке C#, чтобы нам были доступны определенные классы, мы подключаем пространства имен с помощью директивы using, например, `using Avalonia.Controls;`.
+
+Чтобы задействовать элементы в AXAML, мы также подключаем пространства имен. Аттрибуты **xmlns** как раз и представляют собой пространства имен, подключаемые в проект.
+
+Так, пространство имен **https://github.com/avaloniaui** содержит описание и определение большинства элементов управления. Так как является пространством имен по умолчанию, то объявляется без всяких префиксов.
+
+**http://schemas.microsoft.com/winfx/2006/xaml** - это пространство имен, которое определяет некоторые свойства AXAML, например свойство _Name_ или _Key_. Используемый префикс `x` в определении `xmlns:x` означает, что те свойства элементов, которые заключены в этом пространстве имен, будут использоваться с префиксом x - `x:Name` или `x:Key`. Это же пространство имен используется уже в аттрибуте `x:Class="AvaloniaFirst.MainWindow"` - здесь создается ссылка на класс **MainWindow** и соответствующий ему файл кода, куда будет прописываться логика для данного окна приложения.
+
+Это два основных пространства имен. Рассмотрим остальные:
+
+**xmlns:d="http://schemas.microsoft.com/expression/blend/2008"**: предоставляет поддержку атрибутов в режиме дизайнера. Это пространство имен преимущественно предназначено для другого инструмента по созданию дизайна на XAML - Microsoft Expression Blend
+
+**xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"**: обеспечивает режим совместимости разметок XAML.
+
+Важно понимать, что эти пространства имен не эквивалентны тем пространствам имен, которые подключаются при помощи директивы **using** в c#. 
+
+#### Элементы и их атрибуты
+
+XAML предлагает очень простую и ясную схему определения различных элементов и их свойств. Каждый элемент, как и любой элемент XML, должен иметь открывающий и закрывающий теги, как в случае с элементом Window:
+
+```xml
+<Window></Window>
+```
+
+Либо элемент может иметь сокращенню форму с закрывающим слешем в конце, наподобие:
+
+```xml
+<Window />
+```
+
+Но в отличие от элементов xml каждый элемент в XAML соответствует определенному классу C#. Например, элемент **Button** соответствует классу **Avalonia.Controls.Button**. А свойства этого класса соответствуют атрибутам элемента **Button**.
+
+Например, добавим кнопку в создаваемую по умолчанию разметку окна:
+
+```xml
+<Window 
+    ...
+>
+    <Button 
+        x:Name="button1"  
+        Width="100" 
+        Height="30" 
+        Content="Кнопка" />
+</Window>
+```
+
+Сначала идет элемент самого высшего уровня - **Window** (я содержимое вырезал, чтобы не засорять код) и в нем уже определен элемент **Button**, представляющий кнопку.
+
+Для кнопки мы можем определить свойства в виде атрибутов. Здесь определены атрибуты `x:Name` (имя кнопки), `Width`, `Height` и `Content`. 
+
+Подобным образом мы можем определить и другие атрибуты, которые нам нужны. Либо мы можем не определять атрибуты, и тогда они будут использовать значения по умолчанию.
+
+Определив разметку xaml, мы можем запустить проект, и нам отобразится графически весь код xaml - то есть наша кнопка.
+
+![](../img/rider13.png)
+
+#### Специальные символы
+
+При определении интерфейса в XAML мы можем столкнуться с некоторыми ограничениями. В частности, мы не можем использовать специальные символы, такие как знак амперсанда `&`, кавычки `"` и угловые скобки `<` и `>`. Например, мы хотим, чтобы текст кнопки был следующим: `<"Hello">`. У кнопки есть свойство *Content*, которое задает содержимое кнопки. И можно предположить, что нам надо написать так:
+
+```xml
+<Button 
+    Content="<"Hello">" 
+/>
+```
+
+Но такой вариант ошибочен и даже не скомпилирутся. В этом случае нам надо использовать специальные коды символов:
+
+Символ | Код
+:--:|:--:
+`<` | `&lt;`
+`>` | `&gt;`
+`&` | `&amp;`
+`"` | `&quot;`
+
+Например:
+
+```xml
+<Button 
+    Content="&lt;&quot;Hello&quot;&gt;" 
+/>
+```
+
+Еще одна проблема, с которой мы можем столкнуться в XAML - добавление пробелов. Возьмем, к примеру, следующее определение кнопки:
+
+```xml
+<Button>
+    Hello         World
+</Button>
+```
+
+Здесь свойство *Content* задается неявно в виде содержимого между тегами `<Button>....</Button>`. Но несмотря на то, что у нас несколько пробелов между словами "Hello" и "World", XAML по умолчанию будет убирать все эти пробелы. И чтобы сохранить пробелы, нам надо использовать атрибут `xml:space="preserve"`:
+
+```xml
+<Button 
+    xml:space="preserve"
+>
+    Hello         World
+</Button>
+```
+
+### Файлы отделённого кода
+
+При создании нового проекта в дополнение к создаваемому файлу `MainWindow.axaml` создается также файл отделённого кода `MainWindow.axaml.cs`, где, как предполагается, должна находится логика приложения связанная с разметкой из `MainWindow.axaml`. Файлы XAML позволяют нам определить интерфейс окна, но для создания логики приложения, например, для определения обработчиков событий элементов управления, нам все равно придется воспользоваться кодом C#.
+
+По умолчанию в разметке окна используется атрибут `x:Class`:
+
+```xml
+<Window 
+    x:Class="AvaloniaFirst.MainWindow" 
+    ...
+```
+
+Атрибут `x:Class` указывает на класс, который будет представлять данное окно и в который будет компилироваться код в XAML при компиляции. То есть во время компиляции будет генерироваться класс **AvaloniaFirst.MainWindow**, унаследованный от класса **Avalonia.Controls**.
+
+Кроме того в файле отделенного кода `MainWindow.axaml.cs`, который **Rider** создает автоматически, мы также можем найти класс с тем же именем - в данном случае класс **MainWindow**. По умолчанию он имеет некоторый код:
+
+```cs 
+using Avalonia.Controls;
+
+namespace AvaloniaFirst;
+
+public partial class MainWindow : Window
+{
+    public MainWindow()
+    {
+        InitializeComponent();
+    }
+}
+```
+
+По сути пустой класс, но этот класс уже выполняет некоторую работу. Во время компиляции этот класс объединяется с классом, сгенерированном из кода XAML. Чтобы такое слияние классов во время компиляции произошло, класс **MainWindow** определяется как частичный с модификатором **partial**. А через метод *InitializeComponent* класс **MainWindow** вызывает скомпилированный ранее код XAML, разбирает его и по нему строит графический интерфейс окна.
+
+>Здесь проявляется основное отличие **Windows Forms** и **WPF/Avalonia**: в **Windows Forms** код первичен, т.е. при старте приложения **императивно** создаётся интерфейс (вызовами Windows API), а в **WPF** первичен интерфейс, в котором **декларативно** описан внешний вид и при запуске приложения запускается класс окна, который мы можем расширить (инициализировать источники данных и описать логику работы).
+
+#### Взаимодействие кода C# и XAML
+
+В приложении часто требуется обратиться к какому-нибудь элементу управления. Для этого надо установить у элемента в XAML свойство **Name**.
+
+Еще одной точкой взаимодействия между xaml и C# являются события. С помощью атрибутов в XAML мы можем задать события, которые будут связанны с обработчиками в коде C#.
+
+Итак, в разметке главного окна определим два элемента: кнопку и текстовое поле.
+
+>В теге **Window** может быть только один вложенный элемент. Обычно используют **Grid** (подробнее о нём поговорим ниже)
+
+```xml
+<Grid>
+    <TextBox 
+        x:Name="textBox1" 
+        Width="150" 
+        Height="30" 
+        VerticalAlignment="Top" 
+        Margin="20" />
+    <Button 
+        x:Name="button1"  
+        Width="100" 
+        Height="30" 
+        Content="Кнопка" 
+        Click="Button1_OnClick" />
+</Grid>
+```
+
+И изменим класс **MainWindow** добавив в него обработчик нажатия кнопки *Button1_OnClick*:
+
+```cs
+public partial class MainWindow : Window
+{
+    public MainWindow()
+    {
+        InitializeComponent();
+    }
+
+    private void Button1_OnClick(
+        object? sender, 
+        RoutedEventArgs e)
+    {
+        string text = textBox1.Text;
+        if (text != "")
+        {
+            MessageBoxManager
+                .GetMessageBoxStandard(
+                    "Caption", 
+                    text,
+                    ButtonEnum.Ok)
+                .ShowAsync();
+        }
+    }
+}
+```
+
+>В **Avalonia** нет встроенного класса **MessageBox**. Нужно через **NuGet** установить библиотеку `MessageBox.Avalonia`
+
+Определив имена элементов в XAML, затем мы можем к ним обращаться в коде c#: `var text = textBox1.Text`.
+
+В обработчике нажатия кнопки просто выводится сообщение, введенное в текстовое поле. После определения обработчика мы его можем связать с событием нажатия кнопки в xaml через атрибут _Click_: `Click="Button1_OnClick"`. В результате после нажатия на кнопку мы увидим в окне введенное в текстовое поле сообщение.
+
+#### Пространства имен из C# в XAML
+
+По умолчанию в XAML подключается предустановленный набор пространств имен xml. Но мы можем задействовать любые другие пространства имен и их функциональность в том числе и стандартные пространства имен платформы .NET, например, **System** или **System.Collections**.
+
+Например, подключим локальное пространство имен:
+
+```xml
+<Window
+    ...
+    xmlns:local="clr-namespace:AvaloniaFirst"
+```
+
+Локальное пространство имен позволяет подключить все классы, которые определены в коде C# в нашем проекте. Например, добавим в проект следующий класс (напоминаю правило: каждый класс создаётся в отдельном файле):
+
+```cs
+namespace AvaloniaFirst;
+
+public class Phone
+{
+    public string Name { get; set; }
+    public int Price { get; set; }
+      
+    public override string ToString()
+    {
+        return $"Смартфон {this.Name}; цена: {this.Price}";
+    }
+}
+```
+
+Используем этот класс в коде xaml:
+
+```xml
+<Button 
+    x:Name="phoneButton"  
+    Width="250" 
+    Height="40"
+    >
+    <local:Phone
+        Name="Lumia 950" 
+        Price="700" />
+</Button>
+```
+
+Так как пространство имен проекта проецируется на префикс `local`, то все классы проекта можно использовать в форме `local:Название_Класса`. Так в данном случае в качестве содержимого кнопки устанавливается объект **Phone**. 
+
+![](../img/rider14.png)
+
+Для вывода содержимого любого объекта используется метод *ToString*. Этот метод объявлен в классе **Object** и по-умолчанию выводит просто название класса, в нашем случае **AvaloniaFirst.Phone**. Для того, чтобы получить нужную нам информацию об объекте необходимо переопределить метод *ToString*.
+
+Мы можем подключить любые другие пространства имен, классы которых мы хотим использовать в приложении. Например:
+
+```cs
+<Window 
+    ...
+    xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
+    xmlns:sys="clr-namespace:System;assembly=mscorlib"
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^    
+>
+    <Window.Resources>
+        <col:ArrayList x:Key="days">
+            <sys:String>Понедельник</sys:String>
+            <sys:String>Вторник</sys:String>
+            <sys:String>Среда</sys:String>
+            <sys:String>Четверг</sys:String>
+            <sys:String>Пятница</sys:String>
+            <sys:String>Суббота</sys:String>
+            <sys:String>Воскресенье</sys:String>
+        </col:ArrayList> 
+    </Window.Resources>
+</Window>
+```
+
+Здесь определены два дополнительных пространства имен:
+
+```
+xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
+xmlns:sys="clr-namespace:System;assembly=mscorlib"
+```
+
+Благодаря этому нам становятся доступными объекты из пространств имен **System.Collections** и **System**. И затем используя префикс, мы можем использовать объекты, входящие в данные пространства имен: `<col:ArrayList....`
+
+Общий синтаксис подключения пространств имен следующий: `xmlns:Префикс="clr-namespace:Пространство_имен;assembly=имя_сборки"`. Так в предыдущем случае мы подключили пространство имен **System.Collections**, классы которого находятся в сборке **mscorlib**. И данное подключенное пространство имен у нас отображено на префикс col.
+
+## Компоновка
+
+Чтобы перейти уже непосредственно к созданию красивых интерфейсов и их компонентов, сначала необходимо познакомиться с компоновкой. 
+
+Компоновка (**layout**) представляет собой процесс размещения элементов внутри контейнера. Возможно, вы обращали внимание, что одни программы и веб-сайты на разных экранах с разным разрешением выглядят по-разному: где-то лучше, где-то хуже. В большинстве своем такие программы используют жестко закодированные в коде размеры элементов управления. WPF/Avalonia уходят от такого подхода в пользу так называемого "резинового дизайна", где весь процесс позиционирования элементов осуществляется с помощью компоновки.
+
+Благодаря компоновке мы можем удобным образом настроить элементы интерфейса, позиционировать их определенным образом. Например, элементы компоновки в WPF/Avalonia позволяют при ресайзе (сжатии или растяжении) масштабировать элементы, что очень удобно, а визуально не создает всяких шероховатостей типа незаполненных пустот на форме.
+
+В WPF/Avalonia компоновка осуществляется при помощи специальных контейнеров. Фреймворк предоставляет нам следующие контейнеры: **Grid**, **UniformGrid**, **StackPanel**, **WrapPanel**, **DockPanel** и **Canvas**.
+
+Различные контейнеры могут содержать внутри себя другие контейнеры. Кроме данных контейнеров существует еще ряд элементов, такие как **TabPanel**, которые могут включать другие элементы и даже контейнеры компоновки, однако на саму компоновку не столь влияют в отличие от выше перечисленных. Кроме того, если нам не хватает стандартных контейнеров, мы можем определить свои с нужной нам функциональностью.
+
+Контейнеры компоновки позволяют эффективно распределить доступное пространство между элементами, найти для него наиболее предпочтительные размеры.
+
+В WPF/Avalonia при компоновке и расположении элементов внутри окна нам надо придерживаться следующих принципов:
+
+* Нежелательно указывать явные размеры элементов (за исключеним минимальных и максимальных размеров). Размеры должны определяться контейнерами.
+
+* Нежелательно указывать явные позицию и координаты элементов внутри окна. Позиционирование элементов всецело должно быть прерогативой контейнеров. И контейнер сам должен определять, как элемент будет располагаться. Если нам надо создать сложную систему компоновки, то мы можем вкладывать один контейнер в другой, чтобы добиться максимально удобного расположения элементов управления.
+
+### Grid (сетка)
+
+Это наиболее мощный и часто используемый контейнер, напоминающий обычную таблицу. Он содержит столбцы и/или строки, количество которых задает разработчик. Для определения строк используется свойство *RowDefinitions*, а для определения столбцов - свойство *ColumnDefinitions*:
+
+```xml
+<Grid>
+    <Grid.RowDefinitions>
+        <RowDefinition />
+        <RowDefinition />
+        <RowDefinition />
+    </Grid.RowDefinitions>
+    <Grid.ColumnDefinitions>
+        <ColumnDefinition />
+        <ColumnDefinition />
+        <ColumnDefinition />
+    </Grid.ColumnDefinitions>
+</Grid>
+```
+
+Каждая строка задается с помощью вложенного элемента *RowDefinition*. При этом задавать дополнительную информацию необязательно. То есть в данном случае у нас определено в гриде 3 строки.
+
+Каждая столбец задается с помощью вложенного элемента *ColumnDefinition*. Таким образом, здесь мы определили 3 столбца. То есть в итоге у нас получится таблица 3х3.
+
+Для визуального отображения ячеек сетки можно добавить свойство `ShowGridLines="True"`
+
+![](../img/rider15.png)
+
+Чтобы задать позицию элемента управления с привязкой к определенной ячейке **Grid**-а, в разметке элемента нужно прописать значения свойств *Grid.Column* и *Grid.Row*, тем самым указывая, в каком столбце и строке будет находиться элемент. Кроме того, если мы хотим растянуть элемент управления на несколько строк или столбцов, то можно указать свойства *Grid.ColumnSpan* и *Grid.RowSpan*, как в следующем примере:
+
+```xml
+<Grid ShowGridLines="True">
+    <Grid.RowDefinitions>
+        <RowDefinition />
+        <RowDefinition />
+        <RowDefinition />
+    </Grid.RowDefinitions>
+    <Grid.ColumnDefinitions>
+        <ColumnDefinition />
+        <ColumnDefinition />
+        <ColumnDefinition />
+    </Grid.ColumnDefinitions>
+    <Button 
+        Content="Строка 0 Столбец 0"  />
+    <Button 
+        Grid.Column="0" 
+        Grid.Row="1" 
+        Content="Объединение трех столбцов" 
+        Grid.ColumnSpan="3"  />
+    <Button 
+        Grid.Column="2" 
+        Grid.Row="2" 
+        Content="Строка 2 Столбец 2"  />
+</Grid>
+```
+
+То есть у нас получится следующая картина:
+
+Испольуя **WPF**:
+
+![](../img/08007.png)
+
+Испольуя **Avalonia**:
+
+![](../img/rider16.png)
+
+Почему приложения выглядят по-разному, код же одинаковый?
+
+Дело в том, что в WPF стили настроены так, что элементы занимают всё доступное в контейнере место, а в Avalonia по размеру содержимого.
+
+Можно настроить выравнивание так, как это нужно используя атрибуты:
+
+* _HorizontalAlignment_, _VerticalAlignment_ - для позиционирования элемента в контейнере
+* _HorizontalContentAlignment_, _VerticalContentAlignment_ - для позиционирования содержимого элемента
+
+```xml
+<Button 
+    Grid.Column="0" 
+    Grid.Row="1" 
+    Content="Объединение трех столбцов" 
+    Grid.ColumnSpan="3" 
+    HorizontalAlignment="Stretch"
+    VerticalAlignment="Stretch"
+    HorizontalContentAlignment="Center"
+    VerticalContentAlignment="Center"/>
+```
+
+![](../img/rider17.png)
+
+#### Установка размеров
+
+Но если в предыдущем случае у нас строки и столбцы были равны друг другу, то теперь попробуем настроить столбцы по ширине, а строки - по высоте. Есть несколько вариантов настройки размеров.
+
+##### Автоматические размеры
+
+Здесь столбец или строка занимает то место, которое им нужно (по содержимому)
+
+```xml
+<ColumnDefinition Width="Auto" />
+...
+<RowDefinition Height="Auto" />
+```
+
+##### Абсолютные размеры
+
+В данном случае высота и ширина указываются в единицах, независимых от устройства:
+
+```xml
+<ColumnDefinition Width="150" />
+...
+<RowDefinition Height="150" />
+```
+
+Также абсолютные размеры можно задать в пикселях, дюймах, сантиметрах или точках (только в WPF, Avalonia этого не поддерживает):
+
+* _пиксели_: px
+* _дюймы_: in
+* _сантиметры_: cm
+* _точки_: pt (точка в вёрстке это не точка на экране, а 1/72 дюйма)
+
+##### Пропорциональные размеры.
+
+Например, ниже задаются два столбца, второй из которых имеет ширину в четверть от ширины первого:
+
+```xml
+<ColumnDefinition Width="*" />
+<ColumnDefinition Width="0.25*" />
+```
+
+Если строка или столбец имеет высоту, равную `*`, то данная строка или столбец будет занимать все оставшееся место. Если у нас есть несколько сток или столбцов, высота которых равна `*`, то всё доступное место делится поровну между всеми такими сроками и столбцами. Использование коэффициентов (`0.25*`) позволяет уменьшить или увеличить выделенное место на данный коэффициент. При этом все коэффициенты складываются (коэффициент `*` аналогичен `1*`) и затем все пространство делится на сумму коэффициентов.
+
+Например, если у нас три столбца:
+
+```xml
+<ColumnDefinition Width="*" />
+<ColumnDefinition Width="0.5*" />
+<ColumnDefinition Width="1.5*" />
+```
+
+В этом случае сумма коэффициентов равна `1 + 0.5 + 1.5 = 3`. Если у нас грид имеет ширину `300` единиц, то коэфициент `1` будет соответствовать пространству `300 / 3 = 100` единиц. Поэтому первый столбец будет иметь ширину в `100` единиц, второй - `100*0.5=50` единиц, а третий - `100 * 1.5 = 150` единиц.
+
+![](../img/rider18.png)
+
+Можно комбинировать все типы размеров. В этом случае от ширины/высоты грида отнимается ширина/высота столбцов/строк с абсолютными или автоматическими размерами, и затем оставшееся место распределяется между столбцами/строками с пропорциональными размерами:
+
+![](../img/08008.png)
+
+#### GridSplitter
+
+Элемент **GridSplitter** помогает создавать интерфейсы наподобие элемента **SplitContainer** в **WinForms**, только более функциональные. Он представляет собой некоторый разделитель между столбцами или строками, путем сдвига которого можно регулировать ширину столбцов и высоту строк во время выполнения программы. В качестве примера можно привести стандартный интерфейс проводника в Windows, где разделительная полоса отделяет древовидный список папок от панели со списком файлов. Например,
+
+```xml
+<Grid>
+    <Grid.ColumnDefinitions>
+        <ColumnDefinition Width="*" />
+        <ColumnDefinition Width="Auto" />
+        <ColumnDefinition Width="*" />
+    </Grid.ColumnDefinitions>
+    <Button 
+        Grid.Column="0" 
+        Content="Левая кнопка" />
+    <GridSplitter
+        Grid.Column="1" 
+        Width="3" />
+    <Button 
+        Grid.Column="2" 
+        Content="Правая кнопка" />
+</Grid>
+```
+
+![](../img/08009.png)
+
+Двигая центральную линию, разделяющую правую и левую части, мы можем устанавливать их ширину.
+
+Итак, чтобы использовать элемент **GridSplitter**, нам надо поместить его в ячейку в **Grid**-e. По сути это обычный элемент, такой же, как кнопка. Как выше, у нас три ячейки (так как три столбца и одна строка), и **GridSplitter** помещен во вторую ячейку. Обычно строка или столбец, в которые помещают элемент, имеет для свойств _Height_ или _Width_ значение `Auto`.
+
+Если у нас несколько строк, и мы хотим, чтобы разделитель распространялся на несколько строк, то мы можем задать свойство _Grid.RowSpan_:
+
+```xml
+<Grid.ColumnDefinitions>
+    <ColumnDefinition Width="*" />
+    <ColumnDefinition Width="Auto" />
+    <ColumnDefinition Width="*" />
+</Grid.ColumnDefinitions>
+<Grid.RowDefinitions>
+    <RowDefinition></RowDefinition>
+    <RowDefinition></RowDefinition>
+</Grid.RowDefinitions>
+<GridSplitter 
+    Grid.Column="1" 
+    Grid.RowSpan="2" 
+    ShowsPreview="False" 
+    Width="3"
+    HorizontalAlignment="Center" 
+    VerticalAlignment="Stretch" />
+```
+
+В случае, если мы задаем горизонтальный разделитель, то тогда соответственно надо использовать свойство *Grid.ColumnSpan*
+
+Затем нам надо настроить свойства. Во-первых, надо настроить ширину (_Width_) для вертикальных сплитеров и высоту (_Height_) для горизонтальных. Если не задать соответствующее свойство, то сплитер мы не увидим, так как он изначально очень мал.
+
+Затем нам надо задать выравнивание. Если мы хотим, что сплитер заполнял всю высоту доступной области (то есть если у нас вертикальный сплитер), то нам надо установить для свойства *VerticalAlignment* значение `Stretch`.
+
+Если же у нас горизонтальный сплитер, то надо установить свойство *HorizontalAlignment* в `Stretch`
+
+Также в примере выше используется свойство *ShowsPreview*. Если оно равно `False`, то изменение границ кнопок будет происходить сразу же при перемещении сплитера. Если же оно равно `True`, тогда изменение границ будет происходить только после того, как перемещение сплитера завершится, и при перемещении сплиттера мы увидим его проекцию.
+
+### StackPanel
+
+Это более простой элемент компоновки. Он располагает все элементы в ряд либо по горизонтали, либо по вертикали в зависимости от ориентации. Например,
+
+```xml
+<Grid>
+    <StackPanel>
+        <Button 
+            Background="Blue" 
+            Content="1" />
+        <Button 
+            Background="White" 
+            Content="2" />
+        <Button 
+            Background="Red" 
+            Content="3" />
+    </StackPanel>
+</Grid>
+```
+
+В данном случае для свойства *Orientation* по умолчанию используется значение `Vertical`, то есть **StackPanel** создает вертикальный ряд, в который помещает все вложенные элементы сверху вниз. Мы также можем задать горизонтальный стек. Для этого нам надо указать свойство `Orientation="Horizontal"`:
+
+```xml
+<StackPanel Orientation="Horizontal">
+    <Button 
+        Background="Blue" 
+        MinWidth="30" 
+        Content="1" />
+    <Button 
+        Background="White" 
+        MinWidth="30" 
+        Content="2" />
+    <Button 
+        Background="Red" 
+        MinWidth="30" 
+        Content="3" />
+</StackPanel>
+```
+
+При горизонтальной ориентации все вложенные элементы располагаются слева направо. Если мы хотим, чтобы наполнение стека начиналось справа налево, то нам надо задать свойство *FlowDirection*: `<StackPanel Orientation="Horizontal" FlowDirection="RightToLeft">`. По умолчанию это свойство имеет значение `LeftToRight` - то есть слева направо.
+
+### WrapPanel
+
+Эта панель, подобно **StackPanel**, располагает все элементы в одной строке или колонке в зависимости от того, какое значение имеет свойство *Orientation* - `Horizontal` или `Vertical`. Главное отличие от **StackPanel** - если элементы не помещаются в строке или столбце, создаются новые столбец или строка для не поместившихся элементов. Это очень удобно для формирования горизонтального меню.
+
+```xml
+<WrapPanel>
+    <Button 
+        Background="AliceBlue" 
+        Content="Кнопка 1" />
+    <Button 
+        Background="Blue" 
+        Content="Кнопка 2" />
+    <Button 
+        Background="Aquamarine" 
+        Content="Кнопка 3" 
+        Height="30"/>
+    <Button 
+        Background="DarkGreen" 
+        Content="Кнопка 4" 
+        Height="20"/>
+    <Button 
+        Background="LightGreen" 
+        Content="Кнопка 5"/>
+    <Button 
+        Background="RosyBrown" 
+        Content="Кнопка 6" 
+        Width="80" />
+    <Button 
+        Background="GhostWhite" 
+        Content="Кнопка 7" />
+</WrapPanel>
+```
+
+![](../img/08010.png)
+
+В горизонтальном стеке те элементы, у которых явным образом не установлена высота, будут автоматически принимать высоту самого высокого элемента в строке.
+
+Вертикальный **WrapPanel** делается аналогично:
+
+```xml
+<WrapPanel Orientation="Vertical">
+    <Button 
+        Background="AliceBlue" 
+        Content="Кнопка 1" 
+        Height="50" />
+    <Button 
+        Background="Blue" 
+        Content="Кнопка 2" />
+    <Button 
+        Background="Aquamarine" 
+        Content="Кнопка 3" 
+        Width="60"/>
+    <Button 
+        Background="DarkGreen" 
+        Content="Кнопка 4" 
+        Width="80"/>
+    <Button 
+        Background="LightGreen" 
+        Content="Кнопка 5"/>
+    <Button 
+        Background="RosyBrown" 
+        Content="Кнопка 6" 
+        Height="80" />
+    <Button 
+        Background="GhostWhite" 
+        Content="Кнопка 7" />
+    <Button 
+        Background="Bisque" 
+        Content="Кнопка 8" />
+</WrapPanel>
+```
+
+![](../img/08011.png)
+
+
+В вертикальном стеке элементы, у которых явным образом не указана ширина, автоматически принимают ширину самого широкого элемента в колонке.
+
+Мы также можем установить для всех вложенных элементов какую-нибудь определенную ширину (с помощью свойства _ItemWidth_) или высоту (свойство _ItemHeight_):
+
+```xml
+<WrapPanel 
+    ItemHeight="30" 
+    ItemWidth="80" 
+    Orientation="Horizontal"
+>
+    <Button 
+        Background="AliceBlue" 
+        Content="1" />
+    <Button 
+        Background="Blue" 
+        Content="2" />
+    <Button 
+        Background="Aquamarine" 
+        Content="3"/>
+    <Button 
+        Background="DarkGreen" 
+        Content="4"/>
+    <Button 
+        Background="LightGreen" 
+        Content="5"/>
+    <Button 
+        Background="AliceBlue" 
+        Content="6"  />
+    <Button 
+        Background="Blue" 
+        Content="7" />
+</WrapPanel>
+```
+
+![](../img/08012.png)
+
+## Image. Ресурсы
+
+Для добавления ресурсов в проект можно создать в нём каталог (кликнуть правой кнопкой мышки на название проекта и выбрать *Добавить - Создать папку*) и скопировать в него нужный ресурс, в нашем случае картинку.
+
+И добавим картинку в сетку:
+
+```xml
+<Grid ShowGridLines="True">
+    <Grid.ColumnDefinitions>
+        <ColumnDefinition Width="100"/>
+        <ColumnDefinition/>
+    </Grid.ColumnDefinitions>
+    <StackPanel 
+        Orientation="Vertical"
+        VerticalAlignment="Bottom" 
+    >
+        <Image 
+            Source="img/latte-800x800.jpeg"/>
+    </StackPanel>
+</Grid>
+```
+
+Если попытаться запустить проект, то Avalonia выдаст ошибку "Не найден ресурс".
+
+Для того, чтобы добавить картинку в ресурс есть два варианта:
+
+1. В контекстном меню файла картинки в дереве проекта выбрать пункт **Properties** и в поле **Build action** выбрать `AvaloniaResource`
+
+1. Если в проекте много картинок, то проще добавить весь каталог с картинками в ресурсы. Для этого в файл проекта (в режиме просмотра файловой системы открыть файл `<Название проекта>.csproj`)  и в тег **ItemGroup** добавить запись
+
+    ```xml
+    <ItemGroup>
+        <AvaloniaResource Include="img\**"/>
+    </ItemGroup>
+    ```
+
+    ![](../img/rider19.png)
+
+Атрибут **VerticalAlignment** устанавливает вертикальное выравнивание.
+
+Атрибут **Grid.ColumnSpan** (есть и **RowSpan**) позволяет разместить элемент не в одной ячейке Grid-a, а "размазать" на несколько. Например, можно сделать фоновую картинку (как в примере ниже) или горизонтальное меню в верхней строке.
+
+```xml
+<Image 
+    Source="./img/simon.png" 
+    VerticalAlignment="Top" 
+    Grid.ColumnSpan="3"/>
+```
+
+---
+
+## Задание на дом
+
+Реализовать все примеры из лекции. В репозиторий добавить скриншоты результатов работы.
+
+Напоминаю, что для добавления в **MarkDown** картинок используется синтаксис: `![](путь/имя картинки)`
+
+Использовать относительные пути и соблюдать регистр символов (у вас на Windows разницы не будет, но на сервере Linux и имена файлов регистрзависимые).
+
+Например:
+
+```
+![тут можно вписать Alt-строку](./img/some_image.png)
+```
+
+---
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Библиотеки классов](./t7_dll.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Ресурсы](./wpf_resource.md)

+ 180 - 0
articles/tickets.md

@@ -0,0 +1,180 @@
+## Билет №1
+1. Понятие алгоритма. Файлы произвольного доступа (Функции модуля struct). Шаблон Состояние (State)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №2
+1. Свойства алгоритма. Базовые понятия ООП: объект, его свойства и методы, класс, интерфейс. Шаблонный метод (Template Method)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №3
+1. Формы записи алгоритмов. Основные принципы ООП: инкапсуляция, наследование, полиморфизм. Параллельные вычисления.
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №4
+1. Принципы построения алгоритмов. Событийно-управляемая модель программирования. Тестирование (модульное, функциональное, приемочное)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №5
+1. Определение сложности работы алгоритмов. Создание оконных приложений. Популярные форматы файлов. Шаблон Стратегия (Strategy)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №6
+1. Данные (понятие, виды, типы). Ошибки и исключения. Организация доступа к файлам. Файловый объект в Python. Шаблон Посетитель (Visitor)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №7
+1. Основные алгоритмические конструкции. Вспомогательные алгоритмы и процедуры. Декораторы.
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №8
+1. Логические основы алгоритмизации.  Встроенные функции Python. Типы файлов. Шаблон Наблюдатель (Observer)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №9
+1. Логические операции. Регулярные выражения. Рекурсия. Шаблон Хранитель (Memento)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №10
+1. Приоритет логических операций. Шаблоны проектирования, Типы шаблонов
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №11
+1. Законы логических операций. Шаблон Simple Factory (Простая Фабрика)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №12
+1. Таблицы истинности. Шаблон Fabric Method (Фабричный метод)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №13
+1. Эволюция языков программирования. Шаблон Абстрактная фабрика (Abstract Factory)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №14
+1. Классификация языков программирования. Шаблон Строитель (Builder)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №15
+1. Понятие системы программирования. Шаблон Прототип (Prototype)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №16
+1. Методы программирования. Шаблон Одиночка (Singleton)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №17
+1. Жизненный цикл программного обеспечения. Шаблон Адаптер (Adapter)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №18
+1. Типы приложений. Шаблон Мост (Bridge)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №19
+1. Основные свойства ЯП. Шаблон Компоновщик (Composite)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №20
+1. Структура программ. Шаблон Фасад (Facade)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №21
+1. Операторы ЯП. Шаблон Приспособленец (Flyweight)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №22
+1. Системы счисления, Машинное представление чисел. Шаблон Заместитель (Proxy)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №23
+1. Операторы цикла (while, for, range, continue, break, else). Шаблон Цепочка обязанностей (Chain of Responsibility)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №24
+1. Ввод и вывод данных (print, input). Шаблон Команда (Command)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №25
+1. Кодировки символов: ANSI, UTF-8, UNICODE. Шаблон Посредник (Mediator)
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+
+----
+
+## Билет №26
+1. Понятие подпрограммы. Область видимости. Функции в Python. Аргументы функции (Обязательные, ключевые слова,  заданные по-умолчанию). lambda. Секреты хорошей функции. 
+2. Составление блок-схемы по алгоритму
+3. Реализация алгоритма на произвольном языке программирования
+

+ 296 - 0
articles/wpf_filtering.md

@@ -0,0 +1,296 @@
+[Каркас приложения. Модель данных. Привязка данных. Табличный вывод.](./wpf_template.md) | [Содержание](../readme.md) | [Поиск, сортировка](./wpf_search_sort.md)
+
+# Фильтрация данных
+
+В приложениях часто требуется отфильтровать данные либо по словарному полю, либо по каким-либо условиям. 
+
+## Фильтрация по словарю
+
+Суть фильтрации сводится к тому, что отображается не полный список объектов ("кошек"), а отфильтрованный по словарному полю (тип, категория...). Для получения фильтрованного списка реализуем геттер и сеттер для списка кошек:
+
+>Запись типа `public IEnumerable<Cat> catList { get; set; }` на самом деле является так называемым "синтаксическим сахаром", т.е. сокращённой записью для упрощения написания и повышения читабельности кода.
+>
+>При компиляции этот код разворачивается примерно в такой (на самом деле get и set реализуются методами getcatList(){} и setcatList(value){})
+>```cs
+>private IEnumerable<Cat> _catList = null;
+>public IEnumerable<Cat> catList {
+>   get
+>   {
+>       return _catList;
+>   }
+>   set {
+>       _catList = value;
+>   } 
+>}
+>```
+>То есть создаётся приватная переменная для хранения реального значения свойства и методы **get** и **set** для, соответственно, получения и сохранения значения свойства. "value" это новое значение свойства, устанавливаемое при присваивании.
+
+```cs
+public string selectedBreed = "";
+
+private IEnumerable<Cat> _catList = null;
+public IEnumerable<Cat> catList {
+    get
+    {
+        // возвращаем не весь список, а фильтрованный по выбранной породе
+        return _catList
+            .Where(c=>(selectedBreed=="Все породы" || c.breed==selectedBreed));
+    }
+    set {
+        _catList = value;
+    } 
+}
+```
+
+Таким обазом, при присваивании полный список "кошек" будет сохраняться в переменной *_catList*, а при чтении будет возвращаться отфильтрованный список
+
+При работе с БД у нас обычно есть отдельные модели (таблицы) справочников - реализуем в нашем *поставщике данных* метод, возвращающий справочник пород:
+
+1. Сначала создадим класс для элемента справочника (по идее нам было бы достаточно просто массива строк, но мы сразу будем делать "по-взрослому", чтобы сразу пройтись по всем "граблям", которые встретятся при работе с базой данных)
+
+    ```cs
+    public class CatBreed { 
+        public string title { get; set; }
+    }
+    ```
+
+1. Создаем в классе главного окна свойство для хранения справочника
+
+    ```cs
+    public List<CatBreed> catBreedList { get; set; }
+    ```
+
+    Здесь мы выбрали тип **List**, т.к. нам нужен изменяемый список, в который мы добавим элемент "Все породы"
+
+1. В **интерфейс** поставщика данных (**IDataProvider**) добавляем декларацию метода для получения списка пород
+
+    ```cs
+    IEnumerable<CatBreed> getCatBreeds();
+    ```
+
+1. Реализуем этот метод в **LocalDataProvider**
+
+    Этот метод можно реализовать двумя вариантами:
+
+    * можно создать список руками
+
+        ```cs
+        public IEnumerable<CatBreed> getCatBreeds()
+        {
+            return new CatBreed[] {
+                new CatBreed{ title="Дворняжка" },
+                new CatBreed{ title="Шотландская вислоухая" },
+                new CatBreed{ title="Сиамский" }
+            };
+        }
+        ```
+
+        Из плюсов то, что в списке могут быть породы, которых нет в исходных данных (практически это аналог запроса к базе данных). Минус в том, что вы можете пропустить породу, которая есть в исходных данных.
+
+    * можно выбрать существующие породы из исходных данных (этот вариант предпочтительнее)
+
+        >Первоначальный вариант, как оказалось, не работает в **WPF** (видимо метод *DiastinctBy*) появился в C# более старших версий
+        >
+        >```cs
+        >public IEnumerable<CatBreed> getCatBreeds()
+        >{
+        >    return _catList.DistinctBy(cat => cat.breed)
+        >        .Select(cat => new CatBreed { title = cat.breed });
+        >}
+        >```
+        >
+        >Что тут происходит?
+        >
+        >* метод *DistinctBy* выбирает записи с уникальным значением породы
+        >* метод *Select* преобразует исходный список - вместо списка кошек получаем список пород
+
+        ```cs
+        public IEnumerable<CatBreed> getCatBreeds()
+        {
+            return _catList
+                .Select(cat => cat.breed)
+                .Distinct()
+                .Select(breed => new CatBreed { title = breed });
+        }
+        ```
+
+        Что тут происходит?
+
+        Допустим исходный массив выглядит так (массив объектов): 
+
+        ```
+        [
+            {breed: "Порода №1", name: "Имя1"}, 
+            {breed: "Порода №2", name: "Имя2"}, 
+            {breed: "Порода №1", name: "Имя3"} 
+        ]
+        ```
+
+        * метод *Select* преобразует элемент массива в новый объект, и т.к. мы из всего объекта вернули только одно поле, то на выходе у нас будет массив пород:
+
+            ```
+            [
+                "Порода №1", 
+                "Порода №2",
+                "Порода №1" 
+            ]
+            ```
+
+        * метод *Distinct* вернёт массив уникальных занчений:
+
+            ```
+            [
+                "Порода №1", 
+                "Порода №2"
+            ]
+            ```
+
+        * второй вызов *Select* преобразует массив строк в массив объектов типа **CatBreed**
+
+1. Получаем список пород и добавляем в начало "Все породы", чтобы можно было отменить фильтрацию и отображать полный список
+
+    ```cs
+    catBreedList = Globals.dataProvider.getCatBreeds().ToList();
+    catBreedList.Insert(0, new CatBreed { title = "Все породы" });
+    ```
+
+1. Теперь, имея список пород, добавляем в разметку (файл .xaml) выпадающий список для выбор породы (во WrapPanel):
+
+    ```xml
+    <Label 
+        Content="Порода:"
+        VerticalAlignment="Center"/>
+
+    <ComboBox
+        Name="BreedFilterComboBox"
+        SelectionChanged="BreedFilterComboBox_SelectionChanged"
+        VerticalAlignment="Center"
+        MinWidth="100"
+        SelectedIndex="0"
+        ItemsSource="{Binding catBreedList}">
+
+        <ComboBox.ItemTemplate>
+            <DataTemplate>
+                <Label 
+                    Content="{Binding title}"/>
+            </DataTemplate>
+        </ComboBox.ItemTemplate>
+    </ComboBox>
+    ```
+
+    Элемент **ComboBox** предназначен для отображения списка *строк*. Для того, чтобы отобразить элементы произвольного списка используется шаблон **ComboBox.ItemTemplate**, в котором можно реализовать произвольный вид элемента списка (вставить картинки, раскрасить и т.п.) и, в нашем случае, в качестве содержимого выбрать свойство объекта для отображения. 
+
+    >Можно сделать проще, используя переопределение метода *getString*, но пока оставим так.
+
+1. В классе главного окна в обработчике события выбора породы (*BreedFilterComboBox_SelectionChanged*) запоминаем выбранную породу
+
+    ```cs
+    selectedBreed = (BreedFilterComboBox.SelectedItem as CatBreed).title;
+    ```
+
+    Свойство *BreedFilterComboBox.SelectedItem* содержит выбранный элемент списка, в нашем случае это объект типа **CatBreed**.
+
+Если сейчас запустить приложение, то выпадающий список будет отображаться, но реации на выбор не будет - дело в том, что визуальная часть не знает, что данные изменились. В одной из прошлых лекции мы упоминали про интерфейс **INotifyPropertyChanged** - реализуем его для нашего окна:
+
+1. Добавляем интерфейс окну
+
+    ```cs
+    public partial class MainWindow : Window, INotifyPropertyChanged
+                                            ^^^^^^^^^^^^^^^^^^^^^^^^
+    ```
+
+1. Реализуем интерфейс
+
+    ```cs
+    public event PropertyChangedEventHandler PropertyChanged;
+    ```
+
+1. Пишем метод, который будет сообщать визуальной части что изменился список кошек
+
+    ```cs
+    private void Invalidate()
+    {
+        if (PropertyChanged != null)
+            PropertyChanged(this, new PropertyChangedEventArgs("catList"));
+    }
+    ```
+
+1. В обработчик события выбора породы добавим вызов этого метода
+
+    ```cs
+    private void BreedFilterComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        selectedBreed = (BreedFilterComboBox.SelectedItem as CatBreed).title;
+        Invalidate();
+    }
+    ```
+
+## Фильтрация по условию
+
+Иногда встречается требование сделать фильтр по условию. Сам принцип фильтрации остается прежним, только список элементов фильтра формируется программно.
+
+Например, сделаем фильтр по возрасту кошек: котята (до года), молодые (1-10) и старые (>10)
+
+1. Для начала сделаем класс для элементов фильтра:
+
+    ```cs
+    public class CatAge { 
+        public string title { get; set; }
+        public int ageFrom { get; set; }
+        public int ageTo { get; set; }
+    }
+    ```
+
+1. Затем создадим список и переменную для хранения выбранного элемента списка. Обратите внимание, тут мы храним не строку, а весь объект.
+
+    ```cs
+    private CatAge selectedAge = null;
+    public IEnumerable<CatAge> catAgeList { get; set; } = new CatAge[]{
+        new CatAge{title="Все возраста", ageFrom=0, ageTo=99},
+        new CatAge{title="Котята", ageFrom=0, ageTo=1},
+        new CatAge{title="Молодые", ageFrom=1, ageTo=10},
+        new CatAge{title="Старые", ageFrom=10, ageTo=99}
+    };
+    ```
+
+1. В разметке меняем привязку   
+
+    ```xml
+    <ComboBox
+        Name="BreedFilterComboBox"
+        SelectionChanged="BreedFilterComboBox_SelectionChanged"
+        VerticalAlignment="Center"
+        MinWidth="100"
+        SelectedIndex="0"
+        ItemsSource="{Binding catAgeList}">
+
+        <ComboBox.ItemTemplate>
+            <DataTemplate>
+                <Label 
+                    Content="{Binding title}"/>
+            </DataTemplate>
+        </ComboBox.ItemTemplate>
+    </ComboBox>
+    ```
+
+1. В обработчике события выбора элемента списка просто запоминаем выбранный элемент
+
+    ```cs
+    private void BreedFilterComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        selectedAge = BreedFilterComboBox.SelectedItem as CatAge;
+        Invalidate();
+    }
+    ```
+
+1. И меняем геттер списка кошек
+
+    ```cs
+    get
+    {
+        return _catList
+            .Where(c=>(c.age>=selectedAge.ageFrom && c.age<selectedAge.ageTo));
+    }
+    ```
+
+[Каркас приложения. Модель данных. Привязка данных. Табличный вывод.](./wpf_template.md) | [Содержание](../readme.md) | [Поиск, сортировка](./wpf_search_sort.md)

+ 212 - 0
articles/wpf_listbox.md

@@ -0,0 +1,212 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Поиск, сортировка](./wpf_search_sort.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Стили, триггеры и темы](./wpf_style.md)
+
+# Вывод данных согласно макета (ListBox, Image).
+
+В реальных проектах **DataGrid** используется редко. Обычно используется компонент **ListBox**. Пример из задания одного из прошедших демо-экзаменов:
+
+![](../img/product_list_layout.jpg)
+
+
+Вверху уже знакомый нам **WrapPanel**, а вот основная информация выводится в виде блоков
+
+Для создания такого макета используется элемент **ListBox**
+
+В разметке вместо **DataGrid** вставляем **ListBox**:
+
+```xml
+<ListBox 
+    Grid.Row="1"
+    Background="White"
+    ItemsSource="{Binding catList}">
+    <!-- 
+        сюда потом вставить ListBox.ItemTemplate 
+    -->
+</ListBox>
+```
+
+Внутри него вставляем шаблон для элемента списка (*ListBox.ItemTemplate*): пока у нас только прямоугольная рамка со скруглёнными углами (в этом макете вроде скрулять не надо, возможно осталось от другого шаблона)
+
+```xml
+<ListBox.ItemTemplate>
+    <DataTemplate>
+        <Border 
+            BorderThickness="1" 
+            BorderBrush="Black" 
+            CornerRadius="5">
+
+            <!-- сюда потом вставить содержимое: grid из трёх колонок -->
+
+        </Border>
+    </DataTemplate>
+</ListBox.ItemTemplate>  
+```
+
+Внутри макета вставляем **Grid** из трёх колонок: для картинки, основного содержимого и ещё чего-нибудь.
+
+```xml
+<Grid 
+    Margin="10" 
+    HorizontalAlignment="Stretch">
+
+    <Grid.ColumnDefinitions>
+        <ColumnDefinition Width="64"/>
+        <ColumnDefinition Width="*"/>
+        <ColumnDefinition Width="auto"/>
+    </Grid.ColumnDefinitions>
+
+    <!-- сюда потом вставить колонки -->
+
+</Grid>
+```
+
+**В первой** колонке выводим изображение:
+
+```xml
+<Image
+    Width="64" 
+    Height="64"
+    Source="{Binding ImageBitmap,TargetNullValue={StaticResource defaultImage}}" />
+```
+
+Обратите внимание, в классе **Cat** нет поля *ImageBitmap*. Для получения картинки я использую вычисляемое свойство *ImageBitmap* - в геттере проверяю есть ли такая картинка, т.к. наличие названия в модели не означает наличие файла на диске. Если файла нет, то возвращается `null` и рисуется картинка по-умолчанию, которую надо зашить в ресурсы приложения.
+
+>В реальных проектах, конечно, изображения получают с сервера, но на демо-экзамене на реализацию этого функционала времени нет, поэтому картинки кладутся в каталог с проектом (не в ресурсы, а в каталог с исполняемым файлом). Нам нужно найти картинки для нашей предметной области и добавить в поле _photo_
+
+Добавьте в класс **Cat** вычисляемое свойство:  
+
+```cs
+public class Cat
+{
+    public Uri? ImageBitmap {
+        get {
+            var imageName = Environment.CurrentDirectory + "/img/" + (photo ?? "");
+            return System.IO.File.Exists(imageName) ? new Uri(imageName) : null;
+        }
+    }
+}
+```
+
+Изображение по-умолчанию задается в ресурсах окна
+
+```xml
+<Window.Resources>
+    <BitmapImage 
+        x:Key='defaultImage' 
+        UriSource='./Images/picture.png' />
+</Window.Resources>
+```
+
+тут, как раз, указывается путь к изображению в ресурсах (в моём случае в приложении создан каталог `Images`)
+
+
+**Во второй** колонке вывожу основную информацию о кошке: _кличку_ и _породу_.
+
+Так как данные выводятся в несколько строк, то заворачиваю их в **StackPanel** (тут можно использовать и **Grid**, но их и так уже много в разметке)
+
+```xml
+<StackPanel
+    Grid.Column="1"
+    Margin="5"
+    Orientation="Vertical">
+
+    <TextBlock 
+        Text="{Binding name}"/>
+
+    <TextBlock 
+        Text="{Binding breed}"/>
+</StackPanel>
+```
+
+**В третьей** колонке выводим возраст
+
+```xml
+<TextBlock 
+    Grid.Column="2"
+    Text="{Binding age}"/>
+```
+
+На данный момент приложение выглядит примерно так
+
+![](../img/cs005.png)
+
+Видно, что размер элемента зависит от содержимого.
+
+Чтобы это исправить нужно добавить в **ListBox** стиль для элемента контейнера, в котором задать горизонтальное выравнивание по ширине:
+
+```xml
+<ListBox
+    Grid.Row="1"
+    Grid.Column="1"
+    ItemsSource="{Binding ProductList}"
+>
+    <ListBox.ItemContainerStyle>
+        <Style 
+            TargetType="ListBoxItem">
+            <Setter 
+                Property="HorizontalContentAlignment"
+                Value="Stretch" />
+        </Style>
+    </ListBox.ItemContainerStyle>
+    ...
+```
+
+Теперь окно должно выглядеть как положено:
+
+![](../img/cs006.png)
+
+# Вывод данных "плиткой"
+
+Такое задание было на одном из прошлых чемпионатов, вполне вероятно что появится и на демо-экзамене.
+
+Компоненты **ListBox** и **ListView** по умолчанию инкапсулируют все элементы списка в специальную панель **VirtualizingStackPanel**, которая располагает все элементы по вертикали. Но с помощью свойства **ItemsPanel** можно переопределить панель элементов внутри списка. 
+
+Мы будем использовать уже знакомую вам **WrapPanel**:
+
+```xml
+<ListBox.ItemsPanel>
+    <ItemsPanelTemplate>
+        <WrapPanel 
+            HorizontalAlignment="Center" 
+            ItemsWidth="200"
+        />
+    </ItemsPanelTemplate>
+</ListBox.ItemsPanel>
+```
+
+>Атрибут *HorizontalAlignment* используем, чтобы "плитки" центрировались.
+
+![](../img/01072.png)
+
+Как видим, элементы отображаются горизонтальным списком, но нет переноса. Для включения переноса элементов нужно в **ListBox** отключить горизонтальный скролл, добавив атрибут `ScrollViewer.HorizontalScrollBarVisibility="Disabled"`:
+
+![](../img/01073.png)
+
+Свойство *ItemContainerStyle* уже не нужно и его можно убрать.
+
+Размеры наших элементов по-прежнему зависят от содержимого - тут надо править шаблон (ширина элемента **Grid** в **DataTemplate**).
+
+![](../img/listbox001.png)
+
+Итоговая разметка для вывода "плиткой" должна выглядеть примерно так:
+
+```xml
+<ListBox
+    ItemsSource="{Binding catList}"
+    ScrollViewer.HorizontalScrollBarVisibility="Disabled" 
+>
+    <ListBox.ItemsPanel>
+        <ItemsPanelTemplate>
+            <WrapPanel 
+                HorizontalAlignment="Center" />
+        </ItemsPanelTemplate>
+    </ListBox.ItemsPanel>
+    
+    <ListBox.ItemTemplate>
+        ...
+```
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Поиск, сортировка](./wpf_search_sort.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Стили, триггеры и темы](./wpf_style.md)

+ 454 - 0
articles/wpf_resource.md

@@ -0,0 +1,454 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Обзор типов оконных приложений в C#. Знакомство со структурой проекта WPF/Avalonia. Компоновка. Image. Ресурсы.](./t8_win_app.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Привязка (Binding). Интерфейс INotifyPropertyChanged. Форматирование значений привязки и конвертеры значений.](./t8_binding.md)
+
+
+# Ресурсы
+
+## Концепция ресурсов в WPF
+
+В WPF важное место занимают **ресурсы**. В данном случае под ресурсами подразумеваются не дополнительные файлы (или **физические ресурсы**), как, например, аудиофайлы, файлы с изображениями, которые добавляются в проект. Здесь речь идет о **логических ресурсах**, которые могут представлять различные объекты - элементы управления, кисти, коллекции объектов и т.д. Логические ресурсы можно установить в коде XAML или в коде C# с помощью свойства Resources. Данное свойство опредлено в базовом классе **FrameworkElement**, поэтому его имеют большинство классов WPF.
+
+В чем смысл использования ресурсов? Они повышают эффективность: мы можем определить один раз какой-либо ресурс и затем многократно использовать его в различных местах приложения. В связи с этим улучшается поддержка - если возникнет необходимость изменить ресурс, достаточно это сделать в одном месте, и изменения произойдут глобально в приложении.
+
+Свойство _Resources_ представляет объект **ResourceDictionary** или словарь ресурсов, где каждый хранящийся ресурс имеет определенный ключ.
+
+### Определение ресурсов
+
+Определим ресурс окна и ресурс кнопки:
+
+```xml
+<Window
+    ..
+>
+    <Window.Resources>
+        <SolidColorBrush 
+            x:Key="redStyle" 
+            Color="BlanchedAlmond" />
+         
+        <LinearGradientBrush 
+            x:Key="gradientStyle" 
+            StartPoint="0.5,1" 
+            EndPoint="0.5,0">
+            <GradientStop 
+                Color="LightBlue" 
+                Offset="0" />
+            <GradientStop 
+                Color="White" 
+                Offset="1" />
+        </LinearGradientBrush>
+    </Window.Resources>
+
+    <Grid 
+        Background="{StaticResource redStyle}">
+
+        <Button 
+            x:Name="button1" 
+            MaxHeight="40" 
+            MaxWidth="120" 
+            Content="Ресурсы в WPF" 
+            Background="{StaticResource gradientStyle}">
+            <Button.Resources>
+                <SolidColorBrush 
+                    x:Key="darkStyle" 
+                    Color="Gray" />
+            </Button.Resources>
+        </Button>
+    </Grid>
+</Window>
+```
+
+Здесь у окна определяются два ресурса: **redStyle**, который представляет объект **SolidColorBrush**, и **gradientStyle**, который представляет кисть с линейным градиентом. У кнопки определен один ресурс **darkStyle**, представляющий кисть **SolidColorBrush**. Причем каждый ресурс обязательно имеет свойство `x:Key`, которое и определяе ключ в словаре.
+
+А в свойствах _Background_ соответственно у грида и кнопки мы можем применить эти ресурсы: `Background="{StaticResource gradientStyle}"` - здесь после выражения **StaticResource** идет ключ применяемого ресурса.
+
+### Управление ресурсами в коде C#
+
+Добавим в словарь ресурсов окна градиентную кисть и установим ее для кнопки:
+
+```cs
+// определение объекта-ресурса
+LinearGradientBrush gradientBrush = new LinearGradientBrush();
+gradientBrush.GradientStops.Add(new GradientStop(Colors.LightGray, 0));
+gradientBrush.GradientStops.Add(new GradientStop(Colors.White, 1));
+ 
+// добавление ресурса в словарь ресурсов окна
+this.Resources.Add("buttonGradientBrush", gradientBrush);
+ 
+// установка ресурса у кнопки
+button1.Background = (Brush)this.TryFindResource("buttonGradientBrush");
+// или так
+//button1.Background = (Brush)this.Resources["buttonGradientBrush"];
+```
+
+С помощью свойства _Add()_ объект кисти и его произвольный ключ добавляются в словарь. Далее с помощью метода _TryFindResource()_ мы пытаемся найти ресурс в словаре и установить его в качестве фона. Причем, так как этот метод возвращает object, необходимо выполнить приведение типов.
+
+Всего у **ResourceDictionary** можно выделить следующие методы и свойства:
+
+* Метод **Add(string key, object resource)** добавляет объект с ключом **key** в словарь, причем в словарь можно добавить любой объект, главное ему сопоставить ключ
+
+* Метод **Remove(string key)** удаляет из словаря ресурс с ключом **key**
+
+* Свойство **Uri** устанавливает источник словаря
+
+* Свойство **Keys** возвращает все имеющиеся в словаре ключи
+
+* Свойство **Values** возвращает все имеющиеся в словаре объекты
+
+Для поиска нужного ресурса в коллекции ресурсов у каждого элемента определены методы **FindResource()** и **TryFindResource()**. Она оба возвращают ресурс, соответствующий определенному ключу. Единственное различие между ними состоит в том, что **FindResource()** генерирует исключение, если ресурс с нужным ключом не был найден. А метод **TryFindResource()** в этом случае просто возвращает `null`.
+
+### Разделяемые ресурсы
+
+Когда один и тот же ресурс используется в разных местах, то фактически мы используем один и тот же объект. Однако это не всегда желательно. Иногда необходимо, чтобы примение ресурса к разным объектам различалось. То есть нам необходимо, чтобы при каждом применении создавался отдельный объект ресурса. В этом случае мы можем изспользовать выражение `x:Shared="False"`:
+
+```xml
+<SolidColorBrush 
+    x:Shared="False" 
+    x:Key="redStyle" 
+    Color="BlanchedAlmond" />
+```
+
+### Примеры использования ресурсов
+
+Рассмотрим еще пару примеров применения ресурсов. К примеру, если мы хотим, чтобы ряд кнопок обладал одинаковыми свойствами, то мы можем определить одну общую кнопку в качестве ресурса:
+
+```xml
+<Window 
+    ...
+>
+    <Window.Resources>
+        <SolidColorBrush 
+            x:Key="redStyle" 
+            Color="BlanchedAlmond" />
+ 
+        <LinearGradientBrush 
+            x:Key="gradientStyle" 
+            StartPoint="0.5,1" 
+            EndPoint="0.5,0">
+            <GradientStop 
+                Color="LightBlue" 
+                Offset="0" />
+            <GradientStop 
+                Color="White" 
+                Offset="1" />
+        </LinearGradientBrush>
+
+        <Button 
+            x:Key="resButton" 
+            Background="{StaticResource gradientStyle}">
+            <TextBlock 
+                Text="OK" 
+                FontSize="16" />
+        </Button>
+    </Window.Resources>
+
+    <Grid 
+        Background="{StaticResource redStyle}">
+        <Button 
+            Width="80" 
+            Padding="0" 
+            Height="40" 
+            HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" 
+            Content="{StaticResource resButton}" />
+    </Grid>
+</Window>
+```
+
+Другой пример - определение списка объектов для списковых элементов:
+
+```xml
+<Window
+    ...         
+    xmlns:sys="clr-namespace:System;assembly=mscorlib"
+    xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
+>
+    <Window.Resources>
+        <col:ArrayList x:Key="phones">
+            <sys:String>iPhone 6S</sys:String>
+            <sys:String>Nexus 6P</sys:String>
+            <sys:String>Lumia 950</sys:String>
+            <sys:String>Xiaomi MI5</sys:String>
+        </col:ArrayList>
+    </Window.Resources>
+    <Grid>
+        <ListBox ItemsSource="{StaticResource phones}" />
+    </Grid>
+</Window>
+```
+
+## Статические и динамические ресурсы в WPF
+
+Ресурсы могут быть статическими и динамическими. Статические ресурсы устанавливается только один раз. А динамические ресурсы могут меняться в течение работы программы. Например, у нас есть ресурс кисти:
+
+```xml
+<SolidColorBrush 
+    Color="LightGray" 
+    x:Key="buttonBrush" />
+```
+
+Для установки ресурса в качестве статического используется выражение **StaticResource**:
+
+```xml
+<Button 
+    MaxWidth="80" 
+    MaxHeight="40" 
+    Content="OK" 
+    Background="{StaticResource buttonBrush}" />
+```
+
+А для установки ресурса как динамического применяется выражение **DynamicResource**:
+
+```xml
+<Button 
+    MaxWidth="80" 
+    MaxHeight="40" 
+    Content="OK" 
+    Background="{DynamicResource buttonBrush}" />
+```
+
+Причем один и тот же ресурс может быть и статическим и динамическим. Чтобы посмотреть различие между ними, добавим к кнопке обработчик нажатия:
+
+```xml
+<Button 
+    x:Name="button1" 
+    MaxWidth="80" 
+    MaxHeight="40" 
+    Content="OK"
+    Background="{DynamicResource buttonBrush}"  
+    Click="Button_Click" />
+```
+
+А в файле кода определим в этом обработчике изменение ресурса:
+
+```cs
+private void Button_Click(object sender, RoutedEventArgs e)
+{
+    this.Resources["buttonBrush"] = new SolidColorBrush(Colors.LimeGreen);
+}
+```
+
+И если после запуска мы нажмем на кнопку, то ресурс изменит свой цвет, что приведет к изменению цвета кнопки. Если бы ресурс был бы определен как статический, то изменение цвета кисти никак бы не повлияло на цвет фона кнопки.
+
+В то же время надо отметить, что мы все равно может изменить статический ресурс - для этого нужно менять не сам объект по ключу, а его отдельные свойства:
+
+```cs
+private void Button_Click(object sender, RoutedEventArgs e)
+{
+    // данное изменение будет работать и со статическими ресурсами
+    SolidColorBrush buttonBrush = (SolidColorBrush)this.TryFindResource("buttonBrush");
+    buttonBrush.Color = Colors.LimeGreen;
+}
+```
+
+### Иерархия ресурсов
+
+Еще одно различие между статическими и динамическими ресурсами касается поиска системой нужного ресурса. Так, при определении статических ресурсов ресурсы элемента применяются только к вложенным элементам, но не к внешним контейнерам. Например, ресурс кнопки мы не можем использовать для грида, а только для тех элементов, которые будут внутри этой кнопки. Поэтому, как правило, большинство ресурсов определяются в коллекции **Window.Resources** в качестве ресурсов всего окна, чтобы они были доступны для любого элемента данного окна.
+
+В случае с динамическими ресурсами такого ограничения нет.
+
+### Установка динамических ресурсов в коде C#
+
+Ранее мы рассмотрели, как устанавливать в коде C# статические ресурсы:
+
+```cs
+LinearGradientBrush gradientBrush = new LinearGradientBrush();
+gradientBrush.GradientStops.Add(new GradientStop(Colors.LightGray, 0));
+gradientBrush.GradientStops.Add(new GradientStop(Colors.White, 1));
+this.Resources.Add("buttonGradientBrush", gradientBrush);
+ 
+button1.Background = (Brush)this.TryFindResource("buttonGradientBrush");
+```
+
+Установка динамического ресурса призводится немного иначе:
+
+```cs
+LinearGradientBrush gradientBrush = new LinearGradientBrush();
+gradientBrush.GradientStops.Add(new GradientStop(Colors.LightGray, 0));
+gradientBrush.GradientStops.Add(new GradientStop(Colors.White, 1));
+this.Resources.Add("buttonGradientBrush", gradientBrush);
+ 
+button1.SetResourceReference(Button.BackgroundProperty, "buttonGradientBrush");
+```
+
+Для установки применяется метод **SetResourceReference()**, который есть у большинства элементов WPF. Первым параметром в него передается свойство зависимости объекта, для которого предназначен ресурс, а вторым - ключ ресурса. Общая форма установки:
+
+```
+объект.SetResourceReference(Класс_объекта.Свойство_КлассаProperty, ключ_ресурса);
+```
+
+### Элементы StaticResource и DynamicResource
+
+В ряде случае в разметке XAML бывает удобнее использовать не расширения разметки тип `"{StaticResource}"`, а полноценные элементы **DynamicResource** и **StaticResource**. Например:
+
+```xml
+<Window ...>
+    <Window.Resources>
+        <SolidColorBrush 
+            Color="LimeGreen" 
+            x:Key="buttonBrush" />
+    </Window.Resources>
+
+    <Grid>
+        <Button 
+            x:Name="button1" 
+            MaxWidth="80" 
+            MaxHeight="40" 
+            Content="OK">
+            <Button.Background>
+                <DynamicResource 
+                    ResourceKey="buttonBrush" />
+            </Button.Background>
+        </Button>
+    </Grid>
+</Window>
+```
+
+Элементы **StaticResource** и **DynamicResource** имеют свойство _ResourceKey_, которое позволяет установить ключ применяемого ресурса.
+
+Особенно это эффективно может быть с контейнерами:
+
+```xml
+<Window ...>
+    <Window.Resources>
+        <Button 
+            x:Key="buttonRes" 
+            x:Shared="False" 
+            Content="OK" 
+            MaxHeight="40" 
+            MaxWidth="80" 
+            Background="Azure" />
+    </Window.Resources>
+
+    <StackPanel>
+        <StaticResource ResourceKey="buttonRes" />
+        <StaticResource ResourceKey="buttonRes" />
+        <StaticResource ResourceKey="buttonRes" />
+        <StaticResource ResourceKey="buttonRes" />
+    </StackPanel>
+</Window>
+```
+
+## Словари ресурсов
+
+Мы можем определять ресурсы на уровне отдельных элементов окна, например, как ресурсы элементов Window, Grid и т.д. Однако есть еще один способ определения ресурсов, который предполагает использование **словаря ресурсов**.
+
+Нажмем правой кнопкой мыши на проект и в контекстном меню выберем `Add -> New Item...`, И в окне добавления выберем пункт `Resource Dictionary (WPF)`:
+
+![](../img/wpf_resource_01.png)
+
+
+Оставим у него название по умолчанию `Dictionary1.xaml` и нажмем на кнопку OK.
+
+После этого в проект добавляется новый файл. Он представляет собой обычный xaml-файл с одним корневым элементом **ResourceDictionary**:
+
+```xml
+<ResourceDictionary 
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="clr-namespace:ResourcesApp">
+     
+</ResourceDictionary>
+```
+
+Изменим его код, добавив какой-нибудь ресурс:
+
+```xml
+<ResourceDictionary 
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="clr-namespace:ResourcesApp">
+    <LinearGradientBrush 
+        x:Key="buttonBrush">
+        <GradientStopCollection>
+            <GradientStop 
+                Color="White" 
+                Offset="0" />
+            <GradientStop 
+                Color="Blue" 
+                Offset="1" />
+        </GradientStopCollection>
+    </LinearGradientBrush>
+</ResourceDictionary>
+```
+
+После определения файла ресурсов его надо подсоединить к ресурсам приложения. Для этого откроем файл `App.xaml`, который есть в проекте по умолчанию и изменим его:
+
+```xml
+<Application 
+    x:Class="ResourcesApp.App"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="clr-namespace:ResourcesApp"
+    StartupUri="MainWindow.xaml">
+    <Application.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary 
+                    Source="Dictionary1.xaml" />
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
+    </Application.Resources>
+</Application>
+```
+
+Элемент `ResourceDictionary.MergedDictionaries` здесь представляет колекцию объектов **ResourceDictionary**, то есть словарей ресурсов, которые добавляются к ресурсам приложения. Затем в любом месте приложения мы сможем сослаться на этот ресурс:
+
+```xml
+<Button 
+    Content="OK" 
+    MaxHeight="40" 
+    MaxWidth="80" 
+    Background="{StaticResource buttonBrush}" />
+```
+
+При этом одновременно мы можем добавлять в коллекцию ресурсов приложения множество других словарей или параллельно с ними определять еще какие-либо ресурсы:
+
+```xml
+<Application 
+    x:Class="ResourcesApp.App"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="clr-namespace:ResourcesApp"
+    StartupUri="MainWindow.xaml">
+
+    <Application.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary 
+                    Source="Dictionary1.xaml" />
+                <ResourceDictionary 
+                    Source="Dictionary2.xaml" />
+                <ResourceDictionary 
+                    Source="ButtonStyles.xaml" />
+                <SolidColorBrush 
+                    Color="LimeGreen" 
+                    x:Key="limeButton" />
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
+    </Application.Resources>
+</Application>
+```
+
+### Загрузка словаря ресурсов
+
+Нам необязательно добавлять словарь ресурсов через ресурсы приложения. У объекта **ResourceDictionary** имеется свойство _Source_, через которое мы можем связать ресурсы конкретного элемента со словарем:
+
+```xml
+<Window ...>
+    <Window.Resources>
+        <ResourceDictionary 
+            Source="Dictionary1.xaml" />
+    </Window.Resources>
+    <Grid>
+        <Button 
+            Content="OK" 
+            MaxHeight="40" 
+            MaxWidth="80" 
+            Background="{StaticResource buttonBrush}" />
+    </Grid>
+</Window>
+```
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Обзор типов оконных приложений в C#. Знакомство со структурой проекта WPF/Avalonia. Компоновка. Image. Ресурсы.](./t8_win_app.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Привязка (Binding). Интерфейс INotifyPropertyChanged. Форматирование значений привязки и конвертеры значений.](./t8_binding.md)

+ 114 - 0
articles/wpf_search_sort.md

@@ -0,0 +1,114 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Фильтрация данных](./wpf_filtering.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Вывод данных согласно макета (ListBox, Image)](./wpf_listbox.md)
+
+# Поиск, сортировка
+
+В этой теме мы познакомимся еще с двумя визуальными элементами: **TextBox** (ввод строки для поиска) и **RadioButton** (сортировка по одному полю)
+
+## Поиск
+
+1. В разметке окна (в элемент WrapPanel) добавляем элемент для ввода теста - TextBox
+
+    ```xml
+    <Label 
+        Content="искать" 
+        VerticalAlignment="Center"/>
+    <TextBox
+        Width="200"
+        VerticalAlignment="Center"
+        x:Name="SearchFilterTextBox" 
+        KeyUp="SearchFilter_KeyUp"/>
+    ```    
+
+1. В коде окна создаем переменную для хранения строки поиска и запоминаем её в обработчике ввода текста (SearchFilter_KeyUp)
+
+    ```cs
+    private string searchFilter = ""; 
+
+    private void SearchFilter_KeyUp(object sender, KeyEventArgs e)
+    {
+        searchFilter = SearchFilterTextBox.Text;
+        Invalidate();
+    }
+    ```
+
+1. Дорабатываем геттер списка кошек, чтобы после фильтра по возрасту срабатывал ещё и фильтр по кличке
+
+    ```cs
+    get
+    {
+        // сохраняем во временную переменную полный список
+        var res = _catList;
+
+        // фильтруем по возрасту
+        res = res.Where(c=>(c.Age>=selectedAge.ageFrom && c.Age<selectedAge.ageTo));
+
+        // если фильтр не пустой, то ищем ВХОЖДЕНИЕ подстроки поиска в кличке без учета регистра
+        if(SearchFilter != "")
+            res = res.Where(c => c.Name.IndexOf(
+                searchFilter, 
+                StringComparison.OrdinalIgnoreCase) >= 0);
+
+        return res;
+    }
+    ```
+
+## Сортировка
+
+>Мы, в рамках знакомства с визуальными элементами, будем использовать радио-кнопки, но, если вариантов сортировки более одного, то лучше использовать тот-же выпадающий список
+
+Мы будем сортировать по возрасту
+
+Выбранный вариант будем определять по атрибуту **Tag**. Этот атрибут есть у всех элементов и его тип **объект**. 
+
+1. В разметке добавляем радиокнопки
+
+    ```xml
+    <Label 
+        Content="Возраст:" 
+        VerticalAlignment="Center"/>
+    <RadioButton
+        GroupName="Age"
+        Tag="1"
+        Content="по возрастанию"
+        IsChecked="True"
+        Checked="RadioButton_Checked"
+        VerticalAlignment="Center"/>
+    <RadioButton
+        GroupName="Age"
+        Tag="2"
+        Content="по убыванию"
+        Checked="RadioButton_Checked"
+        VerticalAlignment="Center"/>
+    ```
+
+    У группы радио-кнопок одновременно может быть выбран только один вариант. Группа задается атрибутом **GroupName** 
+
+1. В коде добавляем переменную для хранения варианта сортировки и обработчик смены варианта сортировки
+
+    >Мы в атрибуте **Tag** храним просто уникальное значение, но при необходимости можем *прибиндить* какой-нибудь объект и в обработчике получить его: `(sender as RadioButton).Tag as SomeClass`.
+
+    ```cs
+    private bool sortAsc = true;
+
+    private void RadioButton_Checked(object sender, RoutedEventArgs e)
+    {
+        sortAsc = (sender as RadioButton).Tag.ToString() == "1";
+        Invalidate();
+    }
+    ```
+
+1. И дорабатываем геттер списка кошек
+
+    ```cs
+    ...
+    if (sortAsc) res = res.OrderBy(c=>c.Age);
+    else res = res.OrderByDescending(c => c.Age);
+
+    return res;
+    ```
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Фильтрация данных](./wpf_filtering.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Вывод данных согласно макета (ListBox, Image)](./wpf_listbox.md)

+ 621 - 0
articles/wpf_style.md

@@ -0,0 +1,621 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Вывод данных согласно макета (ListBox, Image)](./articles/wpf_listbox.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Создание окон. Модальные окна](./wpf_window.md)
+
+>Два инициативных студента по итогам прошлой лекции решили сделать переключение вида отображения "список" и "плитка" кнопкой. Нарыли интересную тему про стили...
+
+# [Стили и темы](https://metanit.com/sharp/wpf/10.php)
+
+## Стили
+
+Стили позволяют определить набор некоторых свойств и их значений, которые потом могут применяться к элементам в `xaml`. Стили хранятся в ресурсах и отделяют значения свойств элементов от пользовательского интерфейса. Также стили могут задавать некоторые аспекты поведения элементов с помощью триггеров. Аналогом стилей могут служить каскадные таблицы стилей (CSS), которые применяются в коде html на веб-страницах.
+
+Зачем нужны стили? Стили помогают создать стилевое единообразие для определенных элементов. Допустим, у нас есть следующий код xaml:
+
+```xml
+<StackPanel 
+    x:Name="buttonsStack" 
+    Background="Black" 
+>
+    <Button 
+        x:Name="button1" 
+        Margin="10" 
+        Content="Кнопка 1" 
+        FontFamily="Verdana" 
+        Foreground="White" 
+        Background="Black" />
+    <Button 
+        x:Name="button2" 
+        Margin="10" 
+        Content="Кнопка 2" 
+        FontFamily="Verdana" 
+        Foreground="White" 
+        Background="Black"/>
+</StackPanel>
+```
+
+Здесь обе кнопки применяют ряд свойств с одними и теми же значениями. Однако в данном случае мы вынуждены повторяться. Частично, проблему могло бы решить использование ресурсов:
+
+```xml
+<Window 
+    ...
+>
+    <Window.Resources>
+        <FontFamily 
+            x:Key="buttonFont">
+            Verdana
+        </FontFamily>
+        <SolidColorBrush 
+            Color="White" 
+            x:Key="buttonFontColor" />
+        <SolidColorBrush 
+            Color="Black" 
+            x:Key="buttonBackColor" />
+        <Thickness 
+            x:Key="buttonMargin" 
+            Bottom="10" 
+            Left="10" 
+            Top="10" 
+            Right="10" />
+    </Window.Resources>
+
+    <StackPanel 
+        x:Name="buttonsStack" 
+        Background="Black" >
+        <Button 
+            x:Name="button1" 
+            Content="Кнопка 1"
+            Margin="{StaticResource buttonMargin}"
+            FontFamily="{StaticResource buttonFont}"
+            Foreground="{StaticResource buttonFontColor}"
+            Background="{StaticResource buttonBackColor}" />
+        <Button 
+            x:Name="button2" 
+            Content="Кнопка 2"
+            Margin="{StaticResource buttonMargin}"
+            FontFamily="{StaticResource buttonFont}"
+            Foreground="{StaticResource buttonFontColor}"
+            Background="{StaticResource buttonBackColor}"/>
+    </StackPanel>
+</Window>
+```
+
+Однако в реальности код раздувается, опть же приходится писать много повторяющейся информации. И в этом плане стили предлагают более элегантное решение:
+
+```xml
+<Window 
+    ...
+>
+    <Window.Resources>
+        <Style x:Key="BlackAndWhite">
+            <Setter 
+                Property="Control.FontFamily" 
+                Value="Verdana" />
+            <Setter 
+                Property="Control.Background" 
+                Value="Black" />
+            <Setter 
+                Property="Control.Foreground" 
+                Value="White" />
+            <Setter 
+                Property="Control.Margin" 
+                Value="10" />
+        </Style>
+    </Window.Resources>
+
+    <StackPanel 
+        x:Name="buttonsStack" 
+        Background="Black" >
+        <Button 
+            x:Name="button1" 
+            Content="Кнопка 1"
+            Style="{StaticResource BlackAndWhite}" />
+        <Button 
+            x:Name="button2" 
+            Content="Кнопка 2"
+            Style="{StaticResource BlackAndWhite}"/>
+    </StackPanel>
+</Window>
+```
+
+Результат будет тот же, однако теперь мы избегаем ненужного повторения. Более того теперь мы можем управлять всеми нужными нам свойствами как единым целым - одним стилем.
+
+Стиль создается как ресурс с помощью объекта **Style**, который представляет класс **System.Windows.Style**. И как любой другой ресурс, он обязательно должен иметь ключ. С помощью коллекции **Setters** определяется группа свойств, входящих в стиль. В нее входят объекты **Setter**, которые имеют следующие свойства:
+
+* **Property**: указывает на свойство, к которому будет применяться данный сеттер. Имеет следующий синтаксис: `Property="Тип_элемента.Свойство_элемента"`. Выше в качестве типа элемента использовался **Control**, как общий для всех элементов. Поэтому данный стиль мы могли бы применить и к **Button**, и к **TextBlock**, и к другим элементам. Однако мы можем и конкретизировать элемент, например, **Button**:
+
+    ```xml
+    <Setter 
+        Property="Button.FontFamily" 
+        Value="Arial" />
+    ```
+
+* **Value**: устанавливает значение
+
+Если значение свойства представляет сложный объект, то мы можем его вынести в отдельный элемент:
+
+```xml
+<Style 
+    x:Key="BlackAndWhite">
+    <Setter 
+        Property="Control.Background">
+        <Setter.Value>
+            <LinearGradientBrush>
+                <LinearGradientBrush.GradientStops>
+                    <GradientStop 
+                        Color="White" 
+                        Offset="0" />
+                    <GradientStop 
+                        Color="Black" 
+                        Offset="1" />
+                </LinearGradientBrush.GradientStops>
+            </LinearGradientBrush>
+        </Setter.Value>
+    </Setter>
+    <Setter 
+        Property="Control.FontFamily" 
+        Value="Verdana" />
+    <Setter 
+        Property="Control.Foreground" 
+        Value="White" />
+    <Setter 
+        Property="Control.Margin" 
+        Value="10" />
+</Style>
+```
+
+### TargetType
+
+Hам необязательно прописывать для всех кнопок стиль. Мы можем в самом определении стиля с помощью свойства _TargetType_ задать тип элементов. В этом случае стиль будет автоматически применяться ко всем кнопкам в окне:
+
+```xml
+<Window ...>
+    <Window.Resources>
+        <Style 
+            TargetType="Button">
+            <Setter 
+                Property="FontFamily" 
+                Value="Verdana" />
+            <Setter 
+                Property="Background" 
+                Value="Black" />
+            <Setter 
+                Property="Foreground" 
+                Value="White" />
+            <Setter 
+                Property="Margin" 
+                Value="10" />
+        </Style>
+    </Window.Resources>
+
+    <StackPanel 
+        x:Name="buttonsStack" 
+        Background="Black" >
+        <Button 
+            x:Name="button1" 
+            Content="Кнопка 1"  />
+        <Button 
+            x:Name="button2" 
+            Content="Кнопка 2" />
+    </StackPanel>
+</Window>
+```
+
+Причем в этом случае нам уже не надо указывать у стиля ключ `x:Key` несмотря на то, что это ресурс.
+
+Также если используем свойство _TargetType_, то в значении атрибута _Property_ уже необязательно указывать тип, то есть `Property="Control.FontFamily"`. И в данном случае тип можно просто опустить: `Property="FontFamily"`
+
+Если же необходимо, чтобы к какой-то кнопке не применялся автоматический стиль, то ее стилю присваивают значение null
+
+```xml
+<Button 
+    x:Name="button2" 
+    Content="Кнопка 2" 
+    Style="{x:Null}" />
+```
+
+### Определение обработчиков событий с помощью стилей
+
+Кроме коллекции **Setters** стиль может определить другую коллекцию - **EventSetters**, которая содержит объекты **EventSetter**. Эти объекты позволяют связать события элементов с обработчиками. Например, подключим все кнопки к одному обработчику события **Click**:
+
+```xml
+<Style 
+    TargetType="Button">
+    ...
+    <EventSetter 
+        Event="Button.Click" 
+        Handler="Button_Click" />
+</Style>
+```
+
+Соответственно в файле кода `c#` у нас должен быть определен обработчик **Button_Click**:
+
+```cs
+private void Button_Click(object sender, RoutedEventArgs e)
+{
+    Button clickedButton = (Button)sender;
+    MessageBox.Show(clickedButton.Content.ToString());
+}
+```
+
+### Наследование стилей и свойство _BasedOn_
+
+У класса **Style** еще есть свойство _BasedOn_, с помощью которого можно наследовать и расширять существующие стили:
+
+```xml
+<Window.Resources>
+    <Style x:Key="ButtonParentStyle">
+        <Setter Property="Button.FontFamily" Value="Andy" />
+    </Style>
+    <Style 
+        x:Key="ButtonChildStyle" 
+        BasedOn="{StaticResource ButtonParentStyle}">
+        <Setter 
+            Property="Button.BorderBrush" 
+            Value="Red" />
+        <Setter 
+            Property="Button.FontFamily" 
+            Value="Verdana" />
+    </Style>
+</Window.Resources>
+```
+
+Cвойство _BasedOn_ в качестве значения принимает существующий стиль, определяя его как статический ресурс. В итоге он объединяет весь функционал родительского стиля со своим собственным.
+
+Если в дочернем стиле есть сеттеры для свойств, которые также используются в родительском стиле, как в данном случае сеттер для свойства _Button.FontFamily_, то дочерний стиль переопределяет родительский стиль.
+
+### Стили в C#
+
+В `C#` стили представляют объект **System.Windows.Style**. Используя его, мы можем добавлять сеттеры и устанавливать стиль для нужных элементов:
+
+
+```cs
+public MainWindow()
+{
+    InitializeComponent();
+
+    Style buttonStyle = new Style();
+    buttonStyle.Setters.Add(
+        new Setter { 
+            Property = Control.FontFamilyProperty, 
+            Value = new FontFamily("Verdana") });
+    buttonStyle.Setters.Add(
+        new Setter { 
+            Property = Control.MarginProperty, 
+            Value = new Thickness(10) });
+    buttonStyle.Setters.Add(
+        new Setter { 
+            Property = Control.BackgroundProperty, 
+            Value = new SolidColorBrush(Colors.Black) });
+    buttonStyle.Setters.Add(
+        new Setter { 
+            Property = Control.ForegroundProperty, 
+            Value = new SolidColorBrush(Colors.White) });
+    buttonStyle.Setters.Add(
+        new EventSetter { 
+            Event= Button.ClickEvent, 
+            Handler= new RoutedEventHandler( Button_Click) });
+
+    button1.Style = buttonStyle;
+    button2.Style = buttonStyle;
+}
+```
+
+При создании сеттера нам надо использовать свойство зависимостей, например, `Property = Control.FontFamilyProperty`. Причем для свойства _Value_ у сеттера должен быть установлен объект именно того типа, которое хранится в этом свойстве зависимости. Так, свойство зависимости _MarginProperty_ хранит объект типа **Thickness**, поэтому определение сеттера выглядит следующим образом:
+
+```cs
+new Setter { 
+    Property = Control.MarginProperty, 
+    Value = new Thickness(10) }
+```
+
+## Темы
+
+Стили позволяют задать стилевые особенности для определенного элемента или элементов одного типа. Но иногда возникает необходимость применить ко всем элементам какое-то общее стилевое единообразие. И в этом случае мы можем объединять стили элементов в темы. Например, все элементы могут выполнены в светлом стиле, или, наоборот, к ним может применяться так называемая "ночная тема". Более того может возникнуть необходимость не просто определить общую тему для всех элементов, но и иметь возможность динамически выбирать понравившуюся тему из списка тем. И в данной статье рассмотрим, как это сделать.
+
+Пусть у нас есть окно приложения с некоторым набором элементов:
+
+```xml
+<Window 
+    ...
+    Style="{DynamicResource WindowStyle}"
+>
+    <StackPanel>
+        <ComboBox 
+            x:Name="styleBox" />
+        <Button 
+            Content="Hello WPF" 
+            Style="{DynamicResource ButtonStyle}" />
+        <TextBlock 
+            Text="Windows Presentation Foundation" 
+            Style="{DynamicResource TextBlockStyle}" />
+    </StackPanel>
+</Window>
+```
+
+Для примера здесь определены кнопка, текстовый блок и выпадающий список, в котором позже будут выбираться темы.
+
+К элементам окна уже применяются некоторые стили. Причем следует отметить, что стили указывают на динамические (не статические) ресурсы. Однако сами эти ресурсы еще не заданы. Поэтому зададим их.
+
+Для этого добавим в проект новый файл словаря ресурсов, который назовем `light.xaml`, и определим в нем некоторый набор ресурсов:
+
+```xml
+<ResourceDictionary 
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="clr-namespace:ThemesApp">
+    <Style 
+        x:Key="TextBlockStyle" 
+        TargetType="TextBlock">
+        <Setter 
+            Property="Background" 
+            Value="White" />
+        <Setter 
+            Property="Foreground" 
+            Value="Gray" />
+    </Style>
+    <Style 
+        x:Key="WindowStyle" 
+        TargetType="Window">
+        <Setter 
+            Property="Background" 
+            Value="White" />
+    </Style>
+    <Style 
+        x:Key="ButtonStyle" 
+        TargetType="Button">
+        <Setter 
+            Property="Background" 
+            Value="White" />
+        <Setter 
+            Property="Foreground" 
+            Value="Gray" />
+        <Setter 
+            Property="BorderBrush" 
+            Value="Gray" />
+    </Style>
+</ResourceDictionary>
+```
+
+Здесь указаны все те стили, которые применяются элементами окна.
+
+Но теперь также добавим еще один словарь ресурсов, который назовем `dark.xaml` и в котором определим следующий набор ресурсов:
+
+```xml
+<ResourceDictionary 
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="clr-namespace:ThemesApp">
+    <Style 
+        x:Key="TextBlockStyle" 
+        TargetType="TextBlock">
+        <Setter 
+            Property="Background" 
+            Value="Gray" />
+        <Setter 
+            Property="Foreground" 
+            Value="White" />
+    </Style>
+    <Style 
+        x:Key="WindowStyle" 
+        TargetType="Window">
+        <Setter 
+            Property="Background" 
+            Value="Gray" />
+    </Style>
+    <Style 
+        x:Key="ButtonStyle" 
+        TargetType="Button">
+        <Setter 
+            Property="Background" 
+            Value="Gray" />
+        <Setter 
+            Property="Foreground" 
+            Value="White" />
+        <Setter 
+            Property="BorderBrush" 
+            Value="White" />
+    </Style>
+</ResourceDictionary>
+```
+
+Здесь определены те же самые стили, только их значения уже отличаются. То есть фактически мы создали две темы: для светлого и темного стилей.
+
+Теперь применим эти стили. Для этого изменим файл `MainWindow.xaml.cs` следующим образом:
+
+
+```cs
+public partial class MainWindow : Window
+{
+    public MainWindow()
+    {
+        InitializeComponent();
+
+        List<string> styles = new List<string> { "light", "dark" };
+        styleBox.SelectionChanged += ThemeChange;
+        styleBox.ItemsSource = styles;
+        styleBox.SelectedItem = "dark";
+    }
+
+    private void ThemeChange(object sender, SelectionChangedEventArgs e)
+    {
+        string style = styleBox.SelectedItem as string;
+        // определяем путь к файлу ресурсов
+        var uri = new Uri(style + ".xaml", UriKind.Relative);
+        // загружаем словарь ресурсов
+        ResourceDictionary resourceDict = 
+            Application.LoadComponent(uri) as ResourceDictionary;
+        // очищаем коллекцию ресурсов приложения
+        Application.Current.Resources.Clear();
+        // добавляем загруженный словарь ресурсов
+        Application.Current.Resources.MergedDictionaries.Add(resourceDict);
+    }
+}
+```
+
+К элементу **ComboBox** цепляется обработчик _ThemeChange_, который срабатывает при выборе элемента в списке.
+
+В методе **ThemeChange** получаем выделенный элемент, который представляет название темы. По нему загружаем локальный словарь ресурсов и добавляем этот словарь в коллекцию ресурсов приложения.
+
+В итоге при выборе элемента в списке будет меняться применяемая к приложению тема.
+
+## Переключение вида списка
+
+1. Переносим настройки **ListBox** в ресурсы окна (в том числе и шаблон элемента списка):
+
+    ```xml
+    <Window.Resources>
+        <Style 
+            x:Key="StackStyle" 
+            TargetType="ListBox"
+        >
+            <Setter 
+                Property="ItemsPanel"
+            >
+                <Setter.Value>
+                    <ItemsPanelTemplate>
+                        <StackPanel 
+                            Orientation="Vertical"/>
+                    </ItemsPanelTemplate>
+                </Setter.Value>
+            </Setter>
+
+            <Setter 
+                Property="ItemTemplate"
+            >
+                <Setter.Value>
+                    <DataTemplate>
+                        <Border 
+                            BorderThickness="2" 
+                            BorderBrush="DarkRed" 
+                            CornerRadius="4" 
+                            Margin="4"
+                        >
+                            <Grid>
+                                <Grid.ColumnDefinitions>
+                                    <ColumnDefinition Width="64"/>
+                                    <ColumnDefinition Width="*"/>
+                                    <ColumnDefinition Width="auto"/>
+                                </Grid.ColumnDefinitions>
+                                
+                                <Image 
+                                    Width="64" 
+                                    Height="64" 
+                                    Source="{Binding ImageBitmap}"/>
+                                
+                                <StackPanel 
+                                    Grid.Column="1"  
+                                    Orientation="Vertical" 
+                                    Margin="5,2,0,5"
+                                >
+                                    <TextBlock Text="{Binding name}"/>
+                                    <TextBlock Text="{Binding surname}"/>
+                                </StackPanel>
+
+                                <TextBlock 
+                                    Grid.Column="2"  
+                                    Text="{Binding age}" 
+                                    Margin="0,2,10,0"/>
+                            </Grid>
+                        </Border>
+                    </DataTemplate>
+                </Setter.Value>
+            </Setter>
+        </Style>
+        <Style 
+            x:Key="WrapStyle" 
+            TargetType="ListBox"
+        >
+            <Setter 
+                Property="ItemsPanel"
+            >
+                <Setter.Value>
+                    <ItemsPanelTemplate>
+                        <WrapPanel 
+                            HorizontalAlignment="Center"
+                            ItemWidth="200"/>
+                    </ItemsPanelTemplate>
+                </Setter.Value>
+            </Setter>
+
+            <Setter 
+                Property="ItemTemplate"
+            >
+                <Setter.Value>
+                    <DataTemplate>
+                        <Border 
+                            BorderThickness="1" 
+                            BorderBrush="Black" 
+                            CornerRadius="5" 
+                            Margin="5"
+                        >
+                            <StackPanel 
+                                Orientation="Vertical"
+                            >
+                                <Image 
+                                    Width="200" 
+                                    Source="{Binding ImageBitmap}"/>
+                                <TextBlock 
+                                    Text="{Binding name}"
+                                    HorizontalAlignment="Center"/>
+                                <TextBlock 
+                                    Text="{Binding surname}"
+                                    HorizontalAlignment="Center"/>
+                                <TextBlock 
+                                    Text="{Binding age}"
+                                    HorizontalAlignment="Center"/>
+                            </StackPanel>
+                        </Border>
+                    </DataTemplate>
+                </Setter.Value>
+            </Setter>
+        </Style>
+    </Window.Resources>
+    ```
+
+2. **ListBox**-у задаем стиль по-умолчанию
+
+    ```xml
+    <ListBox
+        x:Name="catListBox"
+        Style="{StaticResource StackStyle}"
+        ...
+    ```
+
+    Убираем настройки для `<ListBox.ItemsPanel>` и `<ListBox.ItemTemplate>`, а для `<ListBox.ItemContainerStyle>` возвращаем (хотя он нужен только для стека и его тоже можно перенести в соответствующий стиль):
+
+    ```xml
+    <ListBox.ItemContainerStyle>
+        <Style 
+            TargetType="ListBoxItem">
+            <Setter 
+                Property="HorizontalContentAlignment"
+                Value="Stretch" />
+        </Style>
+    </ListBox.ItemContainerStyle>
+    ```
+
+3. В верхнюю панель (где у нас элементы управления для фильтраци, поиска и т.п.) добавляем кнопку "Сменить стиль" и в её обработчике переопределяем стиль для **ListBox**-а
+
+    ```cs
+    // храним текущий стиль
+    private string currentStyle = "StackStyle";
+
+    private void ToggleView_Click(object sender, RoutedEventArgs e)
+    {
+        currentStyle = currentStyle == "StackStyle" ? "WrapStyle" : "StackStyle";
+        var newStyle = (Style)TryFindResource(currentStyle)
+        if (newStyle != null)
+            catListBox.Style = newStyle;
+    }
+    ```
+
+---
+
+## Задание на дом
+
+Реализовать все примеры из лекции. В репозиторий добавить скриншоты результатов работы.
+
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Вывод данных согласно макета (ListBox, Image)](./articles/wpf_listbox.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Создание окон. Модальные окна](./wpf_window.md)

+ 201 - 0
articles/wpf_template.md

@@ -0,0 +1,201 @@
+[Элементы управления](./t8_elements.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Фильтрация данных](./wpf_filtering.md)
+
+# Каркас приложения. Модель данных. Привязка данных. Табличный вывод.
+
+## Каркас приложения.
+
+На прошлых занятиях и лекциях мы разработали каркас для наших будущих приложений. Рассмотрим его ещё раз:
+
+![](../img/08033.png)
+
+* В левой колонке **(1)** у нас будет элемент **StackPanel**, объединяющий все три строки. В нём будут находится логотип компании и основное меню приложения. Эта колонка имеет фиксированную ширину.
+
+* В верхней строке правой колонки **(2)** будет расположено контекстное меню для элементов фильтрации, поиска и т.д. Эта строка имеет высоту "Auto", т.е. будет зависеть от содержимого.
+
+* В середине второй колонки (3) будет расположен основной список с данными: элементы **DataGrid** или **ListView**
+
+* В нижней строке второй колонки (4) в перспективе будем располагать счётчики, пагинаторы и т.п. (высота тоже "auto")
+
+Разметка выглядит так:
+
+```xml
+<Grid ShowGridLines="True">
+    <Grid.RowDefinitions>
+        <RowDefinition Height="auto"/>
+        <RowDefinition />
+        <RowDefinition Height="auto"/>
+    </Grid.RowDefinitions>
+    <Grid.ColumnDefinitions>
+        <ColumnDefinition Width="200"/>
+        <ColumnDefinition/>
+    </Grid.ColumnDefinitions>
+
+    <!-- типа логотип компании -->
+    <Image 
+        Source="/Img/simon.png" 
+        Grid.RowSpan="2"/>
+
+    <StackPanel 
+        Orientation="Vertical"
+        Grid.RowSpan="3"
+        VerticalAlignment="Bottom">
+        <Button 
+            x:Name="ExitButton"
+            Content="Выход" 
+            Click="ExitButton_Click"
+            Height="50"/>
+    </StackPanel>
+
+    <WrapPanel
+        Orientation="Horizontal"
+        Grid.Column="1"
+        MinHeight="50">
+        <!-- минимальную высоту я тут поставил, чтобы верхнюю строку сетки было видно. В реальном приложении она не нужна -->
+    </WrapPanel>
+</Grid>
+```
+
+>Для того, чтобы картинки были видны в рантайме нужно их поместить а ресурсы:
+>
+>![](../img/image_resource.png)
+
+## Модель данных
+
+В следующем году мы будем получать данные из базы данных, а пока будем экспериментировать на "кошках". Заодно оформим получение данных в виде интерфейса.
+
+>В заданиях WorldSkills есть требование логически выделять модели. Поэтому создадим в проекте папку **Model** (в контекстном меню *решения* выбрать *добавить - создать папку*)
+>
+>![](../img/08034.png)
+
+1. Создадим класс **Кошка** (в контекстном меню папки *Model* выбрать *Добавить - класс*):
+
+    ```cs
+    namespace WpfTemplate.Model
+    {
+        public class Cat
+        {
+            public string name { get; set; }
+            public int age{ get; set; }
+            public string color { get; set; }
+            // порода
+            public string breed { get; set; }
+            public string photo { get; set; }
+        }
+    }    
+    ```
+
+    >Студия не добавляет модификатор доступа классам - допишите **public**
+
+2. Для работы с данными создадим интерфейс **IDataProvider** (поставщик данных) и класс **LocalDataProvider**, реализующий этот интерфейс
+
+    В интерфейсе нам пока нужен только один метод: получение списка кошек
+
+    ```cs
+    namespace WpfTemplate.Model
+    {
+        interface IDataProvider
+        {
+            IEnumerable<Cat> getCats();
+        }
+    }
+    ```
+
+    Метод *GetCats* класса **LocalDataProvider** возвращает список кошек, созданный программно
+
+    ```cs
+    public class LocalDataProvider : IDataProvider
+    {
+        public IEnumerable<Cat> getCats()
+        {
+            return new Cat[]{
+                new Cat{
+                    age=1,
+                    breed="Дворняжка", 
+                    color="Белый", 
+                    name="Ириска"},
+                new Cat{
+                    age=2,
+                    breed="Шотландская вислоухая", 
+                    color="Коричневый", 
+                    name="Изи"},
+                new Cat{
+                    age=3,
+                    breed="Сиамский", 
+                    color="Цветной", 
+                    name="Макс"}
+            };
+        }
+    }
+    ```
+
+3. В пространстве имен проекта создадим класс **Globals**, в котором объявим публичную статическую переменную *dataProvider* типа **IDataProvider**:
+
+    ```cs
+    class Globals
+    {
+        public static IDataProvider dataProvider;
+    }
+    ```
+
+    Это нужно, чтобы поставщик данных был виден в любом месте проекта. 
+
+4. В конструкторе главного окна (`MainWindow.xaml.cs`) присвоим глобальной переменной *dataProvider* экземпляр класса **LocalDataProvider** и сохраним список кошек в свойстве **CatList**
+
+    ```cs
+    public IEnumerable<Cat> catList { get; set; }
+   
+    public MainWindow()
+    {
+        InitializeComponent();
+        DataContext = this;
+        Globals.dataProvider = new LocalDataProvider();
+        CatList = Globals.dataProvider.getCats();
+    }
+
+    private void ExitButton_Click(object sender, RoutedEventArgs e)
+    {
+        Application.Current.Shutdown();
+    }
+    ```
+
+## Привязка данных. Табличный вывод
+
+Теперь, имея данные для отображения, мы можем разместить в разметке элемент **DataGrid** и "привязать" к нему данные:
+
+```xml
+ <DataGrid
+    Grid.Row="1"
+    Grid.Column="1"
+    CanUserAddRows="False"
+    AutoGenerateColumns="False"
+    ItemsSource="{Binding catList}">
+    <DataGrid.Columns>
+        <DataGridTextColumn
+            Header="Кличка"
+            Binding="{Binding name}"/>
+        <DataGridTextColumn
+            Header="Возраст"
+            Binding="{Binding age}"/>
+        <DataGridTextColumn
+            Header="Цвет"
+            Binding="{Binding color}"/>
+        <DataGridTextColumn
+            Header="Порода"
+            Binding="{Binding breed}"/>
+    </DataGrid.Columns>
+</DataGrid>
+```
+
+* Атрибут *CanUserAddRows="False"* запрещает элементу **DataGrid** добавлять строки с таблицу
+
+* Атриут *AutoGenerateColumns="False"* запрещает автоматическое формирование столбцов таблицы - мы вручную описываем те столбцы, которые хотим видеть.
+
+* Атрибут *ItemsSource="{Binding catList}"* "привязывает" к таблице источник данных - наш список кошек. 
+
+* В колонках таблицы мы уже "привязываемся" к свойствам класса источника данных
+
+Приложение должно выглядеть примерно так:
+
+![](../img/08035.png)
+
+[Элементы управления](./t8_elements.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Фильтрация данных](./wpf_filtering.md)

+ 72 - 0
articles/wpf_window.md

@@ -0,0 +1,72 @@
+Предыдущая лекция | &nbsp; | Следующая лекция
+:----------------:|:----------:|:----------------:
+[Стили, триггеры и темы](./wpf_style.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Создание окон. Модальные окна](./wpf_window.md)
+
+# Создание окна
+
+Более менее сложные проекты не помещаются в одно окно. WPF позволяет добавлять дополнительные окна в проект, либо использовать технологию со страницами (а-ля браузер). 
+
+На этой лекции реализуем первый вариант - создание дополнительного окна и выведем в него подробную информацию об объекте.
+
+>Вспоминаем, что мы должны соблюдать файловую структуру проекта, т.е. все однотипные объекты распихивать по соответствующим папкам. 
+    
+Создадим папку `Windows` в проекте и в неё добавим окно:
+
+![](../img/01071.png)
+
+Название окна должно быть осмысленным и с суффиксом *Window*. У меня получилось *DetailWindow*
+
+>Можно в каталог `Windows` перетащить и главное окно **MainWindow**. Только в этом случае надо в разметке приложения (`App.xaml`) добавить название каталога:
+>
+>```xml
+><Application 
+>   x:Class="mysql.App"
+>   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+>   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+>   xmlns:local="clr-namespace:mysql"
+>   StartupUri="Windows/MainWindow.xaml">
+>               ^^^^^^^^
+>...
+
+Опять же, все окна должны иметь нормальные заголовки. В разметке окна поменяйте атрибут *Title* элемента **Window** (это надо сделать и для основного окна)
+
+Верстку я расписывать не буду - придумайте сами (при выводе списка объектов "плиткой" мы показываем только основную информацию об объекте, в окне с детальной информацией можно показать всё).
+
+В коде окна `DetailWindow.xaml.cs` в конструктор добавьте параметр:
+
+```cs
+// для получения данных из класса окна нам 
+// нужно СВОЙСТВО, определяем его 
+public Cat currentCat {get; set;}
+
+// конструктор класса окна
+public DetailWindow(Cat currentCat)
+                    ^^^^^^^^^^^^^^
+{
+    // и инициализируем в конструкторе
+    this.currentCat = currentCat;
+
+    InitializeComponent();
+}
+```
+
+Для открытия окна с детальной информацией будем использовать двойной клик на элементе списка в основном окне (обработчик события _MouseDoubleClick_ добавьте в **ListBox** самостоятельно)
+
+```cs
+private void caListBox_MouseDoubleClick(
+    object sender, 
+    MouseButtonEventArgs e)
+{
+    // в создаваемое окно передаем выбранного котика
+    var detailWindow = new DetailWindow(
+        catListBox.SelectedItem as Cat);
+
+    detailWindow.ShowDialog();
+}
+```
+
+Метод _ShowDialog_ открывает **модальное** окно (пока оно открыто фокус ввода не может вернуться на основное окно).
+
+Если модальность не важна (логика задачи допускает открытие нескольких окон), то можно использовать метод _Show_.
+
+Добавьте в окно с детальной информацией кнопку "OK" и добавьте ей свойство `IsCancel="true"`, чтобы окно закрывалось по клавише "Escape".

+ 17 - 0
articles/задачки.md

@@ -0,0 +1,17 @@
+Нарисуйте блок-схему:
+- дано 3 числа. Вывести на экран большее из них
+- вычислить значение модуля и квадратного корня из выражения (х-у)
+- подсчитать количество символов во введённом с клавиатуры предложении (цифры, пробелы и знаки препинания не являются символами)
+- дана строка, подсчитать количество пробелов в данной строке
+- даны три числа, возвести в квадрат отрицательные, положительные возвести в куб, вывести результаты
+- даны координаты двух точек, составить алгоритм определяющий, которая из точек находится ближе к началу координат
+- даны два угла треугольника, определить может ли существовать треугольник с такими углами, если да, то является ли он прямоугольным
+- даны два разных числа, меньшее из них заменить половиной из суммы, а большее их утроенным произведением
+- в первый день спортсмен пробежал 10 км, каждый следующий день он пробегал на 10% больше, чем в предыдущий, найти сколько всего он пробежал за неделю
+- амеба каждые два часа делится пополам, найти сколько амеб будет через Х часов
+- найти сумму большего и меньшего числа из трех, используя подпрограммы
+
+Cоставить таблицу истинности для выражения:
+- ~A & ~B | C
+- ~A | B & C
+- A & B | ~C

+ 102 - 0
articles/практика.md

@@ -0,0 +1,102 @@
+[содержание](/readme.md)  
+
+# Практическое задание: Создание распределенной системы оплаты услуг
+
+Система оплаты состоит из нескольких независимых участников:
+* *потребитель* услуг
+* *поставщик* услуг
+* *процессинговый* центр
+* *агент* (терминалы оплаты)
+
+Краткое описание системы: независимые *поставщики* услуг (Мегафон, ТЕЛЕ2, Эр-телеком...) заключают договора с *потребителями* (физические лица). Для облегчения оплаты услуг существуют независимые *процессинговые* центры (Киберплат, ОСМП) и подключенные к ним *агенты* с терминалами оплаты.
+
+Технические задания на элементы системы:
+
+>- для всех обязательное **логгирование**, **тестирование** и **документирование**;
+>- визуальный интерфейс это окно, нарисованное с помощью tkinter или PyQT (можно использовать и другие библиотеки)
+>- хранение данных: JSON, SQLite...
+>- POST-запросы отправляются с помощью модуля **requests** (примеры есть в лекциях)
+>- пример простого HTTP-сервера с базовой авторизацией есть в лекциях
+>- весь обмен по HTTP с Basic Authentication (базовой авторизацией)
+
+**Потребитель**
+* заключает договора (получает номер телефона или договора, УРЛ для получения услуги и протокол взаимодействия) с произвольным количеством *поставщиков*
+* в **визуальном интерфейсе** ведет список договоров (добавление/редактирование/удаление) и эмулирует пользование услугами (кнопочки "позвонить"/"выйти в интернет" в зависимости от выбранного *поставщика*). Для эмуляции пользования услугой соответствующему *поставщику* отправляется **POST-запрос** (параметры задаются при заключении договора).
+* **хранит данные** о договорах в файле
+
+**Поставщик**
+* предоставляет **визуальный интерфейс** для заключения договора с *потребителем* (получить ФИО, паспорт, е-майл; выдать уникальный идентификатор (телефон/договор), УРЛ и протокол обмена)
+* **хранит данные** поставищиков в базе данных
+* заключает договор на оплату услуг с произвольным количеством **процессинговых центров** (предоставляет УРЛ своего HTTP-сервера, логин/пароль для авторизации, поля для заполнения при оплате с регулярными выражениями для проверки)
+* **HTTP-сервер** для эмуляции оказания услуг *потребителям* (все транзакции сохраняются в базе данных) и проверки наличия телефона/номера *процессинговым центром* (логгируются)
+* дополнительно: формирование отчета по оказанным услугам в Excel и отправка на электронную почту *потребителя*
+
+**Процессинговый центр**
+* предоставляет **визуальный интерфейс** для заключения договора с *поставщиками* (получить УРЛ, логин/пароль для авторизации, протокол обмена для проверки номера и зачисления баланса, поля с регулярками) и *агентами* (предоставить уникальный номер договора, логин/пароль доступа к процессингу, протокол обмена, задать комиссию процессинга, пополнить баланс)
+* **хранит данные** о *поставщиках*, *агентах* и транзакциях в базе данных
+* **HTTP-сервер** для приема запросов от *агентов*
+* **POST-запросы** *поставщикам* 
+    * проксирование запросов проверки номера
+    * зачисление баланса (заворачивать в транзакцию)
+* дополнительно: отчеты итоговые и по транзакциям в Excel для *поставщиков* и *агентов*
+
+**Агенты**
+* заключение договора с *процессингом* (получение УРЛа, логина/пароля...)
+* **Визуальный интерфейс** (обязательно на CEF) для эмуляции оплаты *потребителем*
+* **POST-запросы** *процессингу*
+    * получение списка *поставщиков* с реквизитами для оплаты (раз в 30 мин)
+    * получение баланса агента раз в 5 мин (при нулевом балансе блокировать работу терминала)
+    * проверка номера перед оплатой
+    * транзакция после оплаты (номер, сумма...), с учетом комиссии агента
+* **хранить данные** о транзакциях в базе данных
+* дополнительно: подключить реальный купюроприемник CashCode CCNET (реализовать протокол обмена по СОМ-порту и события приема денег)
+
+# ЧТО ДЕЛАТЬ?
+
+* установите Python, Visual Studio Code (VSC)
+* создайте на диске папку для проекта
+* в VSC откройте папку проекта и создайте в ней файл test.py
+* следуя подсказкам VSC установите расширение для Питона и линтер
+* творите...
+
+# Как установить Qt Designer
+В лекциях написана команда ``pip install pyqt5``, но она устанавливает только фреймворк, для установки designer-а установите ``pip install pyqt5-tools``
+          
+На некоторых компьютерах может выдать ошибку "недостаточно прав", там же будет и подсказка, что установить можно только для текущего пользователя с ключом --user
+
+``pip install pyqt5-tools --user``
+
+# Динамическое создание класса
+При реализации процессинга возник вопрос как работать с поставщиками (список поставщиков динамический и заранее неизвестно название класса)
+
+Родил такую иерархию:
+
+* классы поставщиков лежат в подкаталоге providers
+```
+providers\
+  beeline.py
+  mts.py
+```
+* подкаталог providers оформлен как модуль, для этого в него добавляется служебный файл ``__init__.py``, в котором для каждого отлаженного поставщика добавляется импорт
+
+```py
+#__init__.py
+from providers.beeline import Beeline
+...
+```
+
+* теперь в основном модуле мы просто импортируем все объекты из модуля providers и при этом классы, которые мы импортировали в ``__init__.py``, попадают в глобальную область видимости и мы можем создавать экземпляры нужных классов:
+
+```py
+# main.py
+from providers import *
+
+if __name__ == "__main__":
+  # название класса поставщика мы берем из базы или конфига, главное что здесь он в виде строки
+  provider_name = 'Beeline'  
+  #globals() возвращает список объектов
+  provider = globals()[provider_name]()
+```
+
+
+[содержание](/readme.md)  

BIN
doc/[draft] РП Основы АиП.docx


BIN
doc/s1.docx


+ 76 - 0
doc/task.md

@@ -0,0 +1,76 @@
+# Примерное задание на экзамен (И-21)
+
+1. Разработать классы:
+
+    1. Человек (**People**)
+
+        * Фамилия
+        * Имя
+        * Отчество
+
+    2. Преподаватель (**Teacher**), потомок **People**
+        
+    3. Предмет (**Subject**)
+
+        * Название
+        * **Teacher** Преподаватель
+
+    4. Оценка (**Grade**)
+
+        * **Subject** Предмет
+        * Значение (1-5)
+        * Дата
+
+    5. Студент (**Student**), потомок **People**
+
+        * Дата рождения
+        * Пол
+        * Группа
+        * Список оценок
+
+2. Загрузить список преподавателей из файла в формате CSV
+
+    ```csv
+    Колесников,Евгений,Иванович
+    ```        
+3. Загрузить спиок предметов из файла в формате XML (с поиском преподавателя по ФИО)
+
+    ```xml
+    <Subjects>
+        <Subject>
+            <Title>
+                Основы агоритмизации и программирования
+            </Title>
+            <Teacher>
+                Колесников Евгений Иванович
+            </Teacher>
+        </Subject>
+    </Subjects>
+    ```
+
+4. Загрузить список студентов из файла в формате JSON
+
+    ```json
+    [
+        {
+            "FIO": "Иванов Иван Иванович",
+            "BirthDay": "2000-12-31",
+            "Gender": "М",
+            "Group": "И-21"
+        }
+    ]
+    ```
+
+5. Загрузить список оценок студентов из файла в формате CSV
+
+    ```csv
+    Иванов Иван Иванович,Основы агоритмизации и программирования,5,2021-05-31
+    ```    
+
+6. Сформировать список студентов для начисления стипендии (список со средним баллом по всем предметам >= 4)
+
+7. Сформировать анти-рейтинг преподавателей (список со средним баллом по его предметам по возрастанию)
+
+8. Оформить сопроводительную записку в файле readme.md
+
+9. Опубликовать результат в репозитории 

BIN
doc/КТП Основы Алгоритмизации и Программирования.docx


BIN
doc/Логические основы алгоритмизации.pptx


BIN
doc/ОП-04 (Основы алгоритмизации).docx


BIN
doc/РП Основы Алгоритмизации и Программирования.docx


BIN
img/01071.png


BIN
img/01072.png


BIN
img/01073.png


BIN
img/03001.png


BIN
img/03002.png


BIN
img/03003.png


BIN
img/03004.png


BIN
img/03005.png


BIN
img/03006.png


BIN
img/03007.png


BIN
img/03008.png


BIN
img/03009.png


BIN
img/03010.png


BIN
img/03011.png


BIN
img/03012.png


BIN
img/03013.png


BIN
img/03014.png


Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott