<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Николай</title><generator>teletype.in</generator><description><![CDATA[Николай]]></description><image><url>https://teletype.in/files/ec/ec7d87fc-c2a2-49eb-b352-5ca2dd907b01.png</url><title>Николай</title><link>https://teletype.in/@nigushch</link></image><link>https://teletype.in/@nigushch?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=nigushch</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/nigushch?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/nigushch?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Thu, 30 Apr 2026 08:52:06 GMT</pubDate><lastBuildDate>Thu, 30 Apr 2026 08:52:06 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@nigushch/H1Oykl8M4</guid><link>https://teletype.in/@nigushch/H1Oykl8M4?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=nigushch</link><comments>https://teletype.in/@nigushch/H1Oykl8M4?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=nigushch#comments</comments><dc:creator>nigushch</dc:creator><title>Вопросы на собеседованиях по C# и .Net. На позицию junior/middle. Часть 1.</title><pubDate>Fri, 11 Jan 2019 15:34:35 GMT</pubDate><category>Прочее</category><description><![CDATA[Совсем скоро настанет момент, когда нужно будет устраиваться на другую работу, а, соответственно, начнутся и собеседования. Для того чтобы не облажаться получить должность, желательно подготовиться. Поэтому появилась идея создать серию статей, в которых будут собраны различные вопросы, которые могут Вам попасться, и дать на них ответы. Материалы брались из различных источников, список будет в конце. Предлагайте в комментариях свои варианты вопросов, они так же будут добавлены в общий список. Итак, начнем!]]></description><content:encoded><![CDATA[
  <p>Совсем скоро настанет момент, когда нужно будет устраиваться на другую работу, а, соответственно, начнутся и собеседования. Для того чтобы <s>не облажаться</s> получить должность, желательно подготовиться. Поэтому появилась идея создать серию статей, в которых будут собраны различные вопросы, которые могут Вам попасться, и дать на них ответы. Материалы брались из различных источников, список будет в конце. Предлагайте в комментариях свои варианты вопросов, они так же будут добавлены в общий список. Итак, начнем!</p>
  <hr />
  <h2>!!НАШ БЛОГ ПЕРЕЕХАЛ!!</h2>
  <p>Мы создали свой сайт! Все материалы, опубликованные в этом блоге, переехали туда. </p>
  <h3>Наш новый сайт <a href="https://maddevelop.ru/" target="_blank">maddevelop.ru</a></h3>
  <p>А данную статью вы можете найти по ссылке ниже:</p>
  <p><a href="https://maddevelop.ru/Textbook/ViewSelectedArticle?textbookName=C%23&sectionName=%D0%A1%D0%BE%D0%B1%D0%B5%D1%81%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D0%BF%D0%BE%20C%23%20%D0%B8%20.NET&subsectionName=%D0%A1%D0%BE%D0%B1%D0%B5%D1%81%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D0%BD%D0%B0%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8E%20junior%2Fmiddle&articleName=%231%20%D0%91%D0%BB%D0%BE%D0%BA%20%D0%B2%D0%BE%D0%BF%D1%80%D0%BE%D1%81%D0%BE%D0%B2%20(%D0%B2%D0%BE%D0%BF%D1%80%D0%BE%D1%81%D1%8B%201-5)" target="_blank">https://maddevelop.ru/Textbook/ViewSelectedArticle?textbookName=C%23&amp;sectionName=%D0%A1%D0%BE%D0%B1%D0%B5%D1%81%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D0%BF%D0%BE%20C%23%20%D0%B8%20.NET&amp;subsectionName=%D0%A1%D0%BE%D0%B1%D0%B5%D1%81%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D0%BD%D0%B0%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8E%20junior%2Fmiddle&amp;articleName=%231%20%D0%91%D0%BB%D0%BE%D0%BA%20%D0%B2%D0%BE%D0%BF%D1%80%D0%BE%D1%81%D0%BE%D0%B2%20(%D0%B2%D0%BE%D0%BF%D1%80%D0%BE%D1%81%D1%8B%201-5)</a></p>
  <p>P.S. движок teletype.in очень странно интерпретирует ссылку, и пришлось вставлять ее полным текстом</p>
  <hr />
  <h2>Вопрос 1.</h2>
  <p>Есть следующее объявление классов A и B:</p>
  <pre>class A
{
	virtual void Foo()
	{
		Console.Write(&quot;Class A&quot;);
	}
}
class B: A
{
	override void Foo()
	{
		Console.Write(&quot;Class B&quot;);
	}
}
</pre>
  <p>Что выведут на консоль такие вызовы метода Foo():</p>
  <pre>B obj1 = new A();
obj.Foo();

B obj2 = new B();
obj2.Foo();

A obj3 = new B();
obj3.Foo();
</pre>
  <p><strong>Ответ:</strong></p>
  <p>Во-первых ошибка в том, что не указаны модификаторы доступа в определениях метода Foo в классах A и B.</p>
  <blockquote>Нельзя использовать модификатор <code>virtual</code> с Модификаторами <code>static</code>, <code>abstract</code>, <code>private</code>, или <code>override</code>. (ссылка на источник: https://docs.microsoft.com/ru-ru/dotnet/csharp/language-reference/keywords/virtual )</blockquote>
  <p>А по умолчанию при определении метода ему присваивается модификатор private.</p>
  <p>Во-вторых когда создается переменная obj1 типа B и в нее помещается объект типа A</p>
  <pre>B obj1 = new A();
</pre>
  <p>производится неявный downcast, что недопустимо.</p>
  <p>Правильный код бы выглядел вот так</p>
  <pre>class A
{
	public virtual void Foo()
	{
		Console.Write(&quot;Class A&quot;);
	}
}
class B: A
{
	public override void Foo()
	{
		Console.Write(&quot;Class B&quot;);
	}
}
class Program
{
  static void Main()
  {   
    B obj2 = new B();
    obj2.Foo();
    
    A obj3 = new B();
    obj3.Foo();
  } 
}
</pre>
  <p>Тогда результатом будет:</p>
  <pre>B
B
</pre>
  <h2>Вопрос 2.</h2>
  <p>Есть следующая структура:</p>
  <pre>public struct S : IDisposable
{
    private bool dispose;
    public void Dispose()
    {
        dispose = true;
    }
    public bool GetDispose()
    {
        return dispose;
    }
}
</pre>
  <p>Что будет выведено в следующем случае:</p>
  <pre>var s = new S();
using (s)
{
    Console.WriteLine(s.GetDispose());
}
Console.WriteLine(s.GetDispose());
</pre>
  <p>Варианты ответов:</p>
  <ol>
    <li>true, true</li>
    <li>true, false</li>
    <li>false, true</li>
    <li>false, false</li>
  </ol>
  <p><strong>Ответ:</strong></p>
  <p>Правильный ответ под номером 4 (false, false). Это происходит потому, что блок</p>
  <pre>using (s)
{
	...
}
</pre>
  <p>на самом деле выполняется как</p>
  <pre>using (S s2 = s)
{
	...
}
</pre>
  <p>а так как структура это <em>тип значения</em>, а не <em>ссылочный тип</em>, она копируется по значению, а не по ссылке. Поэтому все действия будут происходить над копией, а не над исходным экземпляром.</p>
  <p>На эту тему есть <a href="https://ericlippert.com/2011/03/14/to-box-or-not-to-box/" target="_blank">статья</a>. (скорее всего сделаю перевод этой статьи и опубликую здесь, уж очень она познавательна)</p>
  <h2>Вопрос 3.</h2>
  <p>Есть следующий код:</p>
  <pre>List&lt;Action&gt; actions = new List&lt;Action&gt;();
for(var count=0; count&lt;10; count++)
{
    actions.Add(() =&gt; Console.WriteLine(count));
}
foreach(var action in actions)
{
    action();
}
</pre>
  <p>Что будет выведено на консоль? Варианты ответов:</p>
  <ol>
    <li>0, 1, 2, 3, 4, 5, 6, 7, 8, 9</li>
    <li>10, 10, 10, 10, 10, 10, 10, 10, 10, 10</li>
    <li>Код сгенерирует исключение</li>
    <li>0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,</li>
  </ol>
  <p><strong>Ответ:</strong></p>
  <p>Правильный ответ 2 (выведет 10 раз число 10).</p>
  <p>Это происходит потому, что в <em>цикле for</em> в переменную <em>actions</em> будет записано 10 действий WriteLine(count). После 10 итерации <em>цикла for</em> переменная <em>count</em> примет значение 10. А когда мы будем пробегаться по <em>циклу foreach,</em> мы будем вызывать подряд 10 методов <em>Console.WriteLine(count)</em> из списка <em>actions</em>, где<em> count = 10</em>.</p>
  <h2>Вопрос 4.</h2>
  <p>Что будет выведено на консоль в результате следующих операций:</p>
  <pre>int i = 1;
object obj = i;
++i;
Console.WriteLine(i);
Console.WriteLine(obj);
Console.WriteLine((short)obj);
</pre>
  <p><strong>Ответ:</strong></p>
  <p>Произойдет ошибка на этапе выполнения в последней строке. При упаковке в переменной типа <em>int </em>в переменную типа<em> object</em> распаковка может происходить только в этот же тип, а уже после этого можно производить другие приведения. (<a href="https://blogs.msdn.microsoft.com/ericlippert/2009/03/19/representation-and-identity/" target="_blank">ссылка на пояснение на msdn</a>)</p>
  <h2>Вопрос 5.</h2>
  <p>Что выведет на консоль следующий код:</p>
  <pre>var s1 = string.Format(&quot;{0}{1}&quot;, &quot;abc&quot;, &quot;cba&quot;);
var s2 = &quot;abc&quot; + &quot;cba&quot;;
var s3 = &quot;abccba&quot;;
 
Console.WriteLine(s1 == s2);
Console.WriteLine((object)s1==(object)s2);
Console.WriteLine(s2==s3);
Console.WriteLine((object)s2==(object)s3);
</pre>
  <p>Варианты ответов:</p>
  <ol>
    <li>true, false, true, true</li>
    <li>true, true, true, true</li>
    <li>true, false, true, false</li>
    <li>true, false, false, false</li>
  </ol>
  <p><strong>Ответ:</strong></p>
  <p>Ухххх...а с этим вопросом можно сломать мозг, здесь затрагивается тема интернирования строк. Ответом будет номер 1 (true false true true). Это происходит потому, что в C# есть пул интернирования в котором хранятся ссылки на строки, подробнее эта тема будет рассмотрена отдельно. </p>
  <p>А вкратце можно ответить так. При запуске программы среда CLR находит все строковые литералы и добавляет их в пул интернирования до ее выполнения. При создании новых переменных с этими значениями в них помещается только ссылка из пула. Операция конкатенации воспринемается средой CLR в качестве целого строкового литерала (это будет наглядно если посмотреть код в программе ildasm), а вот операция String.Format создаёт строку уже в момент выполнения программы и переменная получит другую ссылку, хотя по значению они будут равны. Поэтому при сравнении ссылок на s1 и s2 программа нам выдает false, а в этот же момент s2 и s3 true.</p>
  <h2>Итог:</h2>
  <p>В этой статье были рассмотрены только пять вопросов, но дальше-больше, ждите обновлений, а также пишите в комментариях варианты своих вопросов и.</p>
  <p><strong>Источники:</strong></p>
  <p><a href="https://metanit.com/sharp/interview/1.1.php" target="_blank">Metanit. Собеседование по C#. Часть 1</a></p>
  <p><a href="https://metanit.com/sharp/interview/1.2.php" target="_blank">Metanit. Собеседование по C#. Часть 2</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@nigushch/ryWUPUIlN</guid><link>https://teletype.in/@nigushch/ryWUPUIlN?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=nigushch</link><comments>https://teletype.in/@nigushch/ryWUPUIlN?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=nigushch#comments</comments><dc:creator>nigushch</dc:creator><title>Программа для добавления/удаления строки к именам файлов в папке. Часть 2.</title><pubDate>Tue, 18 Dec 2018 19:34:02 GMT</pubDate><category>Проекты</category><category>WPF</category><description><![CDATA[Теперь рассмотрим процесс реализации класса ViewModel, наследуемого от ViewModelBase. Это абстрактный класс, который содержится в библиотеке MVVM Light. С помощью него реализуется обновление данных в свойствах, привязанных к элементам UI, а так же возможность передачи сообщений между объектами.]]></description><content:encoded><![CDATA[
  <p>Теперь рассмотрим процесс реализации класса ViewModel, наследуемого от ViewModelBase. Это абстрактный класс, который содержится в библиотеке MVVM Light. С помощью него реализуется обновление данных в свойствах, привязанных к элементам UI, а так же возможность передачи сообщений между объектами.</p>
  <p>Начнем с описания логики работы первой вкладки программы «Добавление к имени файла»</p>
  <h2>Реализация добавления строки к имени файла.</h2>
  <p>Во-первых нам нужно связать TextBox’ы с соответствующими свойствами в MainViewModel через алгоритм привязки.</p>
  <p><strong>xaml</strong></p>
  <pre>&lt;!--Путь до папки с файлами--&gt;
&lt;TextBox Grid.Column=&quot;0&quot; Name=&quot;PathToFolderAddString_tb&quot; 
   Text=&quot;{Binding PathToFilesForAddString_Renamer, Mode=TwoWay,
   UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
&lt;!--Строка до имени файла--&gt;
&lt;TextBox Name=&quot;AddTextBefore_tb&quot; 
   Text=&quot;{Binding StringBeforeFileName_Renamer, 
   UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
&lt;!--Строка после имени файла--&gt;
&lt;TextBox Name=&quot;AddTextAfter_tb&quot; 
   Text=&quot;{Binding StringAfterFileName_Renamer, 
   UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
&lt;!--Строка в имени файла--&gt;
&lt;TextBox Name=&quot;InsertTextOnPosition_td&quot; 
   Text=&quot;{Binding InsertTextOnPosition_Renamer, 
   UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
</pre>
  <p><strong>C#</strong></p>
  <pre>//Путь до папки в которой переименовываются файл
private string _pathToFilesForAddString_Renamer;
public string PathToFilesForAddString_Renamer
  {
    get { return _pathToFilesForAddString_Renamer; }
    set
      {
        _pathToFilesForAddString_Renamer = value;
        //Если меняется путь, то автоматическое 
        //слежение за папкой выключается
        if (AutoWatchingForFolder)
        {
          AutoWatchingForFolder = false;
        }
        RaisePropertyChanged(nameof(PathToFilesForAddString_Renamer));
      }
   }
//Строка которую нужно вставить до имени файла
private string _stringBeforeFileName_Renamer = &quot;&quot;;
public string StringBeforeFileName_Renamer
  {
   get { return _stringBeforeFileName_Renamer; }
   set
    {
      _stringBeforeFileName_Renamer = value;
      RaisePropertyChanged(nameof(StringBeforeFileName_Renamer));
    }
  }
//Строка которую нужно вставить после имени файла
private string _stringAfterFileName_Renamer = &quot;&quot;;
public string StringAfterFileName_Renamer
  {
    get { return _stringAfterFileName_Renamer; }
    set
    {
      _stringAfterFileName_Renamer = value;
      RaisePropertyChanged(nameof(StringAfterFileName_Renamer));
    }
  }
//Строка которую нужно вставить на определенную позицию
private string _insertTextOnPosition_Renamer = &quot;&quot;;
public string InsertTextOnPosition_Renamer
  {
    get { return _insertTextOnPosition_Renamer; }
    set
    {
      _insertTextOnPosition_Renamer = value;
      RaisePropertyChanged(nameof(InsertTextOnPosition_Renamer));
    }
  }
</pre>
  <p>Так как в WPF нет элемента NumericUpDown, пришлось его искать в сторонних библиотеках. Выбор пал на <a href="https://github.com/xceedsoftware/wpftoolkit" target="_blank">Xceed</a>, она есть как платная, так и бесплатная. Подключить ее можно через менеджер пакетов NuGet. Чтобы использовать ее элементы, нам нужно прописать:</p>
  <p><strong>xaml</strong></p>
  <pre>xmlns:xctk=”http://schemas.xceed.com/wpf/xaml/toolkit”
</pre>
  <p>После этого можно создать сам элемент и привязать его к свойству</p>
  <p><strong>xaml</strong></p>
  <pre>&lt;xctk:IntegerUpDown Minimum=&quot;0&quot; Margin=&quot;10 5 0 5&quot; 
  Text=&quot;{Binding InputPosition_Renamer, 
  UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
</pre>
  <p><strong>C#</strong></p>
  <pre>//Позиция начиная с которой нужно вставить строку
private int _inputPosition_Renamer = 0;
public int InputPosition_Renamer
    {
      get { return _inputPosition_Renamer; }
      set
      {
        _inputPosition_Renamer = value;
        RaisePropertyChanged(nameof(InputPosition_Renamer));
      }
    }
</pre>
  <p>Помимо технологии привязки данных, в WPF предусмотрено связывание действий элементов Button с методами класса ViewModel через систему команд. Для этого существует вспомогательный класс RelayCommand, наследуемый от интерфейса ICommand. Его реализация уже встроена в библиотеку MVVM Light, но его можно реализовать и самостоятельно. <em>(Если нужна будет реализация напишу в комментариях)</em>.</p>
  <p>Для того чтобы связать команду и визуальный элемент напишем следующее:</p>
  <p><strong>xaml</strong></p>
  <pre>&lt;Button Name=&quot;RenameFilesOneTime_btn&quot; 
  Command=&quot;{Binding RenameFiles_Renaimer_Command}&quot; 
  Content=&quot;Переименовать файлы&quot; /&gt;
</pre>
  <p><strong>C#</strong></p>
  <pre>//Команда для переименовывания файлов
private RelayCommand _renameFiles_Renamer_Command;
public RelayCommand RenameFiles_Renaimer_Command
    {
      get
      {
        return _renameFiles_Renamer_Command ?? 
          (_renameFiles_Renamer_Command = new RelayCommand(() =&gt;
        {
          RenameFilesMethod_Renamer(PathToFilesForAddString_Renamer);
        }));
      }
    }
</pre>
  <p>Метод <strong>RenameFilesMethod_Renamer(string pathToFolder)</strong> принимает на вход путь до папки, в которой лежат файлы.</p>
  <pre>//Метод для переименовывания файлов в папке
private void RenameFilesMethod_Renamer(string pathToFolder)
 {
   if (CheckEnteredPath(pathToFolder) == 0)
   {
     return;
   }

   //Процесс переименования
   IEnumerable&lt;FileInfo&gt; filesToRename = Directory.GetFiles(pathToFolder)
                                            .Select(f =&gt; new FileInfo(f));
   foreach (FileInfo fileInfo in filesToRename)
   {
     string newFileName = RenameFileMethod_Renamer(fileInfo);
     if (newFileName == &quot;errorExceptionCheckIt&quot;)
     {
       return;
     }
     Messenger.Default.Send(new AddInfoInLogFileMessage()
     {
       MessageForLogFile = &quot;Файл &quot; + fileInfo.Name + &quot; переименован в &quot; + newFileName,
       TextColorInLog = &quot;#000000&quot;
     });
   }
 }
</pre>
  <p>Метод <strong>RenameFileMethod_Renamer(FileInfo inputFile)</strong> сделан для удобства, потому что позже будет реализован алгоритм постоянного сканирования папки на наличие новых файлов и этот метод пригодится.</p>
  <pre>//Метод для переименовывания одного файла
private string RenameFileMethod_Renamer(FileInfo inputFile)
 {
   try
   {
    string newFileName;
    newFileName = $@&quot;{StringBeforeFileName_Renamer}{
               Path.GetFileNameWithoutExtension(inputFile.Name).Insert(InputPosition_Renamer,
               InsertTextOnPosition_Renamer)
             }&quot; +
            $@&quot;{StringAfterFileName_Renamer}{inputFile.Extension}&quot;;
    string newFileFullPath = Path.Combine(inputFile.DirectoryName, newFileName);
    File.Move(inputFile.FullName, newFileFullPath);
     return newFileName;
   }
   catch (Exception e)
   {
     DispatcherHelper.CheckBeginInvokeOnUI(() =&gt;
     {
       Messenger.Default.Send(new AddInfoInLogFileMessage()
       {
         MessageForLogFile = e.Message,
         TextColorInLog = &quot;#000000&quot;
       });
     });
   }
   return &quot;errorExceptionCheckIt&quot;;
 }
</pre>
  <p>Метод <strong>CheckEnteredPath(string pathToFolder)</strong> проверяет, правильный ли путь был передан на вход метода, выполняющего переименование.</p>
  <pre>private int CheckEnteredPath(string pathToFolder)
 {
   //Проверка наличия указанного пут����
   if (pathToFolder == &quot;&quot;)
   {
     Messenger.Default.Send(new AddInfoInLogFileMessage() { MessageForLogFile = &quot;Не задан путь до папки!&quot;, TextColorInLog = &quot;#B22222&quot; });
     return 0;
   }
   //Проверка существования выбранной папаки
   if (!System.IO.Directory.Exists(pathToFolder))
   {
     Messenger.Default.Send(new AddInfoInLogFileMessage() { MessageForLogFile = &quot;Такой папки не существует!&quot;, TextColorInLog = &quot;#B22222&quot; });
     return 0;
   }
   return 1; 
 }
</pre>
  <p>Чтобы получить список файлов находящихся в выбранной папке, мы используем метод</p>
  <pre>Directory.GetFiles(pathToFolder)
</pre>
  <p>Который на выход получает путь до папки а возвращает имена файлов в ней.</p>
  <p>Далее эти имена мы заносим в строго типизированный перечислитель:</p>
  <pre>IEnumerable&lt;FileInfo&gt; filesToRename = Directory.GetFiles(pathToFolder).Select(f =&gt; new FileInfo(f));
</pre>
  <p>Тип FileInfo предоставляет свойства и методы для работы с файлами.</p>
  <p>Если переименование проходит успешно, то мы отправляем сообщение из класса ViewModel в MainWindow.xaml.cs</p>
  <p><strong>Код во ViewModel</strong></p>
  <pre>Messenger.Default.Send(new AddInfoInLogFileMessage()
  {
    MessageForLogFile = &quot;Файл &quot; + fileInfo.Name + &quot; переименован в &quot; + newFileName,
    TextColorInLog = &quot;#000000&quot;
 });
</pre>
  <p><strong>В конструкторе класса MainWindow.xaml.cs</strong></p>
  <pre>Messenger.Default.Register&lt;AddInfoInLogFileMessage&gt;(this, x =&gt;
{
  LogText_tb.AppendText(DateTime.Now.ToShortDateString() + &quot; &quot; + DateTime.Now.ToLongTimeString() + 
   &quot; &quot; + x.MessageForLogFile + &quot;\n&quot;);
  LogText_tb.ScrollToEnd();
  ScrollViewerForLog.ScrollToBottom();
});
</pre>
  <p>Чтобы отправлять сообщения мы создали вспомогательный класс <strong>AddInfoInLogFileMessage</strong> в папке Helpers. Он содержит в себе свойства, которые мы можем соотнести с параметрами, передаваемыми через сообщения.</p>
  <pre>class AddInfoInLogFileMessage
 {
   public string MessageForLogFile { get; set; }
   public string TextColorInLog { get; set; }
 }
</pre>
  <p>Таким образом был описан алгоритм для добавления строки к именам всех файлов в выбранной папке. Теперь же покажу, как реализовать автоматическое отслеживание появления изменение структуры папки.</p>
  <h2>Автоматическое переименование файлов в папке.</h2>
  <p>UI содержит элемент CheckBox, который включает поток для отслеживания изменений в структуре папки (добавление, удаление, переименование файлов в ней)</p>
  <pre>&lt;CheckBox Margin=&quot;0 10 0 10&quot; IsChecked=&quot;{Binding AutoWatchingForFolder,
          UpdateSourceTrigger=PropertyChanged}&quot;&gt;
  &lt;TextBlock Text=&quot;Автоматически переименовывать файлы при появлении в папке&quot; TextWrapping=&quot;Wrap&quot;/&gt;
    &lt;i:Interaction.Triggers&gt;
      &lt;i:EventTrigger EventName=&quot;Checked&quot;&gt;
         &lt;i:InvokeCommandAction 
            Command=&quot;{Binding StartWatchingForFolder_Renaimer_Command}&quot;/&gt;
      &lt;/i:EventTrigger&gt;
      &lt;i:EventTrigger EventName=&quot;Unchecked&quot;&gt;
          &lt;i:InvokeCommandAction 
            Command=&quot;{Binding StopWatchingForFolder_Renaimer_Command}&quot;/&gt;
       &lt;/i:EventTrigger&gt;
    &lt;/i:Interaction.Triggers&gt; 
&lt;/CheckBox&gt;
</pre>
  <p>С помощью библиотеки <strong>System.Windows.Interactivity</strong> удаётся привязать события элемента UI к командам класса ViewModel.</p>
  <pre>//Начать отслеживать появление изменений в папке
public RelayCommand StartWatchingForFolder_Renaimer_Command
 { get
    {
      return _startWatchingForFolder_Ranaimer_Command ?? 
             (_startWatchingForFolder_Ranaimer_Command
              = new RelayCommand(() =&gt;
              {
                //Проверка на наличие пути
                if (CheckEnteredPath(PathToFilesForAddString_Renamer) == 0)
                {
                  AutoWatchingForFolder = false;
                  return;
                }
                if (watcher == null)
                {
                  watcher = new FileSystemWatcher();
                  watcher.Path = PathToFilesForAddString_Renamer;
                  watcher.NotifyFilter = NotifyFilters.LastAccess
                              | NotifyFilters.FileName 
                              | NotifyFilters.DirectoryName;
                }
                watcher.Changed += (sender, args) =&gt;
                {
                  //Отправка сообщения об изменении состояния файла
                  SendMessageInLogFromWatcher(args);
                };
                watcher.Created += (sender, args) =&gt;
                {
                  //Отправка сообщения об изменении состояния файла
                  SendMessageInLogFromWatcher(args);
                  FileInfo inputFile = new FileInfo(args.FullPath);
                  Thread.Sleep(50);
                  if (RenameFileMethod_Renamer(inputFile) == &quot;errorExceptionCheckIt&quot;)
                  { return; }
                };
                watcher.Deleted += (sender, args) =&gt;
                {
                  //Отправка сообщения об изменении состояния файла
                  SendMessageInLogFromWatcher(args);
                };
                watcher.Renamed += (sender, args) =&gt;
                {
                  //Отправка сообщения об изменении состояния файла
                  SendMessageInLogFromWatcher(args);
                };
                watcher.EnableRaisingEvents = true;
              }));
      }
    }
//Остановить отслеживание изменений в папке
public RelayCommand StopWatchingForFolder_Renaimer_Command
{
 get
   {
    return _stopWatchingForFolder_Ranaimer_Command ?? (_stopWatchingForFolder_Ranaimer_Command = new RelayCommand(() =&gt;
      {
       if (watcher!=null)
        {
         watcher.EnableRaisingEvents = false;
         watcher = null;
        }
      }));
   }
}
</pre>
  <p>В отслеживании помогает специальный класс <strong>FileSystemWatcher</strong>, который ожидает поступления уведомления от файловой системы об изменениях и инициализирует при этом события.</p>
  <p>После создания экземпляра этого класса его нужно настроить</p>
  <pre>//Создание экземпляра класса
watcher = new FileSystemWatcher();
//Прописываем путь до папки
watcher.Path = PathToFilesForAddString_Renamer;
//Добавляем необходимые фильтры (В зависимости от них система будет выбирать на какие изменения ей нужно реагировать)
watcher.NotifyFilter = NotifyFilters.LastAccess
   | NotifyFilters.FileName 
   | NotifyFilters.DirectoryName;
</pre>
  <p>Дальше нужно подписаться на события, которые возникают при изменениях в каталоге.</p>
  <pre>//Событие создания файла в каталоге. Самое нужное нам событие.
watcher.Created += (sender, args) =&gt;
   {
//Отправка сообщения об изменении состояния файла
    SendMessageInLogFromWatcher(args);
    FileInfo inputFile = new FileInfo(args.FullPath);
    Thread.Sleep(50);
    if (RenameFileMethod_Renamer(inputFile) == &quot;errorExceptionCheckIt&quot;)
     { return; }
    };
</pre>
  <p>Так же здесь есть метод <strong>SendMessageInLogFromWatcher()</strong> который в качестве входного параметра получает экземпляр класса FileInfo и выводит в окно логов информацию о проведенной операции.</p>
  <pre>//Отправка сообщения об изменении состояния файла
public void SendMessageInLogFromWatcher(FileSystemEventArgs args)
{
  if (args is RenamedEventArgs)
  {
    DispatcherHelper.CheckBeginInvokeOnUI(() =&gt;
    {
      Messenger.Default.Send(new AddInfoInLogFileMessage()
      {
        MessageForLogFile = &quot;Файл: &quot; + (args as RenamedEventArgs).OldName
                           + &quot; был переименован в &quot; + args.Name,
        TextColorInLog = &quot;#000000&quot;
      });
    });
    return;
  }
  DispatcherHelper.CheckBeginInvokeOnUI(() =&gt;
  {
    Messenger.Default.Send(new AddInfoInLogFileMessage()
    {
      MessageForLogFile = &quot;Файл: &quot; + args.Name + &quot; был &quot; + args.ChangeType,
      TextColorInLog = &quot;#000000&quot;
    });
  });
}
</pre>
  <p>Класс <strong>DispatcherHelper</strong> предоставляет возмодность отправлять информацию в виде сообщений между потоками.</p>
  <p>Можно сказать, что реализация автоматического добавления строки к названию файла, который появляется в папке, завершена.</p>
  <h2>Удаление строки из названия файла.</h2>
  <p>Эта часть программы очень похожа на процесс добавления строк, поэтому описывать ее второй раз не вижу смысла. И так получился большой пост, перегруженный большим количеством кода. Как удалять строки можно посмотреть в исходном коде программы.</p>
  <h2>Итоги</h2>
  <p>Подведем маленький итог. Было создано простенькое приложение, которое изменяет имена всех файлов в выбранной папке. Удалось даже <s>прикрутить</s>реализовать паттерн MVVM, такая архитектура пригодится в случае расширения возможностей программы.</p>
  <p>Спасибо за внимание. По возникающим вопросам пишите в комментарии.</p>
  <p>И, как обещал ссылка проекта на <a href="https://github.com/NiGushch/UsefulUtility" target="_blank">GitHub</a>.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@nigushch/HJ5KRSre4</guid><link>https://teletype.in/@nigushch/HJ5KRSre4?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=nigushch</link><comments>https://teletype.in/@nigushch/HJ5KRSre4?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=nigushch#comments</comments><dc:creator>nigushch</dc:creator><title>Программа для добавления/удаления строки к именам файлов в папке. Часть 1.</title><pubDate>Mon, 17 Dec 2018 17:55:39 GMT</pubDate><media:content medium="image" url="https://teletype.in/files/cb/cbf3cbdb-e2ce-4da3-8d61-2c9068d7d367.png"></media:content><category>Проекты</category><category>WPF</category><description><![CDATA[<img src="https://teletype.in/files/4e/4e9912d5-dc1b-4e14-b1ae-41095b8901d8.jpeg"></img>Как-то раз столкнулся с такой проблемой. Понадобилось добавить к именам всех файлов в папке одну и ту же строку, но так как файлов было очень много (10-ки тысяч) вручную это сделать не представлялось возможным. Вот и пришла в голову идея написать маленькую программу, которая добавляет строку к имени файла и может удалять символы, начиная с введенного индекса.]]></description><content:encoded><![CDATA[
  <p>Как-то раз столкнулся с такой проблемой. Понадобилось добавить к именам всех файлов в папке одну и ту же строку, но так как файлов было очень много (10-ки тысяч) вручную это сделать не представлялось возможным. Вот и пришла в голову идея написать маленькую программу, которая добавляет строку к имени файла и может удалять символы, начиная с введенного индекса.</p>
  <p>Программу писал на основе технологии WPF. Для того чтобы потренироваться с применением архитектурных паттернов, принял решение реализовать проект с использованием MVVM (о нем расскажу в отдельной статье). Unit тестирование не добавлял, в случае развития проекта (есть идеи добавить еще какой-нибудь функционал) добавлю.</p>
  <h2>Создание проекта и определение структуры.</h2>
  <p>В VS создаем новый проект &quot;Приложение WPF&quot;. Через «Диспетчер пакетов NuGet» устанавливаем библиотеку MVVM Ligth (хорошая библиотека, которая создает уже определенную логику и макет проекта). Она нам создаст папку ViewModel, в которой находятся два класса ViewModelLocator (помогает с Binding’ом данных и создает экземпляр класса ViewModel) и MainViewModel (в нем содержится логика для представления MainWindow), они нам нужны, для того чтобы развязать представление от бизнес логики приложения. Добавим так же папки Views и Helpers, в первую переместим все файлы представлений (он у нас пока один), а во второй будут храниться классы, которые помогут отправлять Messages из ViewModel во View.</p>
  <figure class="m_custom">
    <img src="https://teletype.in/files/4e/4e9912d5-dc1b-4e14-b1ae-41095b8901d8.jpeg" width="912" />
  </figure>
  <p>Таким образом, мы реализовали структуру проекта (в простом варианте, без классов моделей).</p>
  <h2>Создание представления.</h2>
  <p>Графический интерфейс у нас будет состоять из двух вкладок:</p>
  <p>• добавление строк к имени файла</p>
  <p>• удаление символов из имени файла</p>
  <p>На рисунке ниже показано, как выглядит первая вкладка</p>
  <figure class="m_custom">
    <img src="https://teletype.in/files/3f/3f3f3c91-7bc5-4e3f-87f9-78a952069947.jpeg" width="896" />
  </figure>
  <p>На ней есть поле для ввода пути до папки, в которой будут переименовываться файлы, а справа кнопка для вызова диалогового окна выбора папки.</p>
  <pre>&lt;Grid&gt;
    &lt;Grid.ColumnDefinitions&gt;
        &lt;ColumnDefinition/&gt;
        &lt;ColumnDefinition Width=&quot;30&quot;/&gt;
    &lt;/Grid.ColumnDefinitions&gt;
    &lt;TextBox Grid.Column=&quot;0&quot; Name=&quot;PathToFolderAddString_tb&quot; 
             Text=&quot;{Binding PathToFilesForAddString_Renamer, 
             Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
    &lt;Button Grid.Column=&quot;1&quot; Name=&quot;BrowseFolderAddString_btn&quot; Content=&quot;...&quot; 
             Click=&quot;BrowseFolderAddString_btn_OnClick&quot;/&gt;
&lt;/Grid&gt;
</pre>
  <p>Пришлось использовать класс FolderBrowserDialog из библиотеки System.Windows.Forms, так как в WPF нет встроенного класса с таким диалоговым окном.</p>
  <p>Код для метода Click кнопки BrowseFolderAddString_btn.</p>
  <pre>private void BrowseFolderAddString_btn_OnClick(object sender, RoutedEventArgs e)
    {
      FolderBrowserDialog fbd = new FolderBrowserDialog();
      fbd.Description = &quot;Выберите путь до папки с файлами&quot;;
      if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
      {
        PathToFolderAddString_tb.Text = fbd.SelectedPath;
      }
    }
</pre>
  <p>Ниже расположены три TextBox’а, в которые нужно ввести текст для добавления.</p>
  <pre>&lt;Label Content=&quot;Добавить текст до:&quot;/&gt;
&lt;TextBox Name=&quot;AddTextBefore_tb&quot; Text=&quot;{Binding StringBeforeFileName_Renamer, 
         UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
&lt;Label Content=&quot;Добавить текст после:&quot;/&gt;
&lt;TextBox Name=&quot;AddTextAfter_tb&quot; Text=&quot;{Binding StringAfterFileName_Renamer,
         UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
&lt;StackPanel Orientation=&quot;Horizontal&quot;&gt;
&lt;Label Content=&quot;Вставить текст на позицию: &quot;/&gt;
&lt;xctk:IntegerUpDown Minimum=&quot;0&quot; Margin=&quot;10 5 0 5&quot; Text=&quot;{Binding InputPosition_Renamer,
         UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
&lt;/StackPanel&gt;
&lt;TextBox Name=&quot;InsertTextOnPosition_td&quot; Text=&quot;{Binding InsertTextOnPosition_Renamer,
         UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
</pre>
  <p>Справа Button для выполнения переименования и CheckBox, который запускает непрерывное сканирование на появление событий связанных с выбранной папкой (такие события как: добавление, удаление, переименование файлов).</p>
  <pre>&lt;GroupBox Header=&quot;Запуск переименовывания&quot; Grid.Column=&quot;1&quot; Margin=&quot;5&quot; Padding=&quot;5&quot;&gt;
     &lt;StackPanel Margin=&quot;10&quot;&gt;
         &lt;Button Name=&quot;RenameFilesOneTime_btn&quot; Command=&quot;{Binding RenameFiles_Renaimer_Command}&quot; 
                 Content=&quot;Переименовать файлы&quot; /&gt;
         &lt;CheckBox Margin=&quot;0 10 0 10&quot; IsChecked=&quot;{Binding AutoWatchingForFolder,
                 UpdateSourceTrigger=PropertyChanged}&quot;&gt;
         &lt;TextBlock Text=&quot;Автоматически переименовывать файлы при появлении в папке&quot; 
                 TextWrapping=&quot;Wrap&quot;/&gt;
             &lt;i:Interaction.Triggers&gt;
                &lt;i:EventTrigger EventName=&quot;Checked&quot;&gt;
                   &lt;i:InvokeCommandAction 
                       Command=&quot;{Binding StartWatchingForFolder_Renaimer_Command}&quot;/&gt;
                &lt;/i:EventTrigger&gt;
                &lt;i:EventTrigger EventName=&quot;Unchecked&quot;&gt;
                   &lt;i:InvokeCommandAction 
                       Command=&quot;{Binding StopWatchingForFolder_Renaimer_Command}&quot;/&gt;
                   &lt;/i:EventTrigger&gt;
                &lt;/i:Interaction.Triggers&gt;
         &lt;/CheckBox&gt;
      &lt;/StackPanel&gt;
&lt;/GroupBox&gt;
</pre>
  <p>Все изменения будут отображаться в логах.</p>
  <p>При добавлении новых файлов в папку они будут автоматически переименовываться по заданному правилу.</p>
  <p>Вторая вкладка выполнена в аналогичном стиле.</p>
  <figure class="m_custom">
    <img src="https://teletype.in/files/37/37121979-ee70-433f-9145-ee958b866f03.jpeg" width="893" />
  </figure>
  <p>Есть два TextBox для ввода начального индекса и количества удаляемых символов. Также есть возможность выбора порядка удаления, с начала или с конца имени файла.</p>
  <p>Код представлен ниже.</p>
  <pre>&lt;Grid&gt;
    &lt;Grid.ColumnDefinitions&gt;
        &lt;ColumnDefinition/&gt;
        &lt;ColumnDefinition/&gt;
    &lt;/Grid.ColumnDefinitions&gt;

&lt;!--Путь к файлам и настройка переименования--&gt;
    &lt;GroupBox Header=&quot;Настройки&quot; Margin=&quot;5&quot; Padding=&quot;2&quot;&gt;
       &lt;StackPanel&gt;
           &lt;Label Content=&quot;Путь к файлам:&quot;/&gt;
           &lt;Grid&gt;
              &lt;Grid.ColumnDefinitions&gt;
                 &lt;ColumnDefinition/&gt;
                 &lt;ColumnDefinition Width=&quot;30&quot;/&gt;
              &lt;/Grid.ColumnDefinitions&gt;
              &lt;TextBox Grid.Column=&quot;0&quot; Name=&quot;PathToFolderDeleteString_tb&quot;
                      Text=&quot;{Binding PathToFilesForDeleteString_Renamer,
                      Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
              &lt;Button Grid.Column=&quot;1&quot; Name=&quot;BrowseFolderDeleteString_btn&quot; Content=&quot;...&quot; 
                  Click=&quot;BrowseFolderDeleteString_btn_OnClick&quot;/&gt;
            &lt;/Grid&gt;
            &lt;Label Content=&quot;Порядок удаления:&quot;/&gt;
            &lt;StackPanel Orientation=&quot;Horizontal&quot;&gt;
                &lt;RadioButton GroupName=&quot;turn&quot; Content=&quot;Начало=&gt;Конец&quot; Margin=&quot;5&quot; IsChecked=&quot;{Binding BeginningOrEnd}&quot; /&gt;
                &lt;RadioButton GroupName=&quot;turn&quot; Content=&quot;Конец=&gt;Начало&quot; Margin=&quot;5&quot;/&gt;
            &lt;/StackPanel&gt;
            &lt;Label Content=&quot;Начать с:&quot;/&gt;
            &lt;TextBox Name=&quot;StartPoint_tb&quot; Text=&quot;{Binding StartPointForDelete_Renaimer}&quot;/&gt;
            &lt;Label Content=&quot;Сколько удалять:&quot;/&gt;
            &lt;TextBox Name=&quot;NumCharForDelete_tb&quot; Text=&quot;{Binding NumOfCharForDelete_Renaimer}&quot;/&gt;
       &lt;/StackPanel&gt;
    &lt;/GroupBox&gt;

&lt;!--Запуск переименования--&gt;
&lt;GroupBox Header=&quot;Запуск переименовывания&quot; Grid.Column=&quot;1&quot; Margin=&quot;5&quot; Padding=&quot;5&quot;&gt;
     &lt;StackPanel Margin=&quot;10&quot;&gt;
        &lt;Button Name=&quot;RenameFilesDeleteString_btn&quot; Command=&quot;{Binding DeleteStringFromFileName_Command}&quot; 
                Content=&quot;Переименовать файлы&quot; /&gt;
     &lt;/StackPanel&gt;
&lt;/GroupBox&gt;
&lt;/Grid&gt;
</pre>
  <p>На этом этапе заканчивается процесс создания визуальной части приложения. Если есть какие-то вопросы, то пишите в комментарии</p>
  <p>Во второй части я расскажу как реализовать класс ViewModel и логику выполнения переименования файлов. Ссылка на исходный код программы будет там же.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@nigushch/S1I2mEelN</guid><link>https://teletype.in/@nigushch/S1I2mEelN?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=nigushch</link><comments>https://teletype.in/@nigushch/S1I2mEelN?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=nigushch#comments</comments><dc:creator>nigushch</dc:creator><title>Мой первый пост.</title><pubDate>Thu, 13 Dec 2018 19:31:58 GMT</pubDate><description><![CDATA[Приветствую вас, друзья!]]></description><content:encoded><![CDATA[
  <p>Приветствую вас, друзья!</p>
  <p>Первый пост писать всегда сложно, много что хочется рассказать, но обо всем по порядку.</p>
  <p>Меня зовут Николай и мне 25 лет. Я окончил университет с красным дипломом (инженер радиотехник), и пошел работать по специальности (как я думал). Примерно год назад стало понятно, что выбранная профессия мне не нравится. Я не чувствовал, что развиваюсь как профессионал. Нужно было что-то менять.</p>
  <p>В университете мне не нравилось программирование, казалось, что оно очень сложное и не постижимое. Но время шло, сознание менялось, появился интерес (может потому, что все таки пришлось программировать). Только язык программирования был подстать специальности, инженерный (это был LabView). Хотя и получалось на нем работать и довольно не плохо, даже пару программ удалось реализовать, не лежала душа к нему, хотелось что-то более общераспространенное, да и когда появились мысли об уходе стал больше смотреть в сторону &quot;С&quot; подобных языков. И тут мой взгляд упал на детище компании Microsoft, а именно на языке C#.</p>
  <p>Начал с изучения основ по книге Троелсена &quot;<a href="https://www.ozon.ru/context/detail/id/137598683/" target="_blank">Язык программирования C# 5.0 и платформа .NET 4.5</a>&quot;, повторял все изложенные там примеры, но все равно было не легко все понять с первого раза (особенно многопоточность и работу с делегатами). Параллельно решал задачки на <a href="http://CodeWars.com" target="_blank">CodeWars</a>. На это ушло парочка месяцев. Оставался вопрос, где искать практику, ведь использую только книги научиться невозможно.</p>
  <p>Захотел попробовать себя в разработке мобильных приложений. Благо у .Net есть технология Xamarin, которая позволяет создавать как кроссплатформенные, так и нативные приложения для мобильных устройств. Но позанимался этим не долго, где то 2 месяца пока был в командировке, сделал небольшой калькулятор для Android. А когда вернулся на работу уже не было времени на Xamarin, да и технология то не сильно востребована.</p>
  <p>Далее приступил к разработке приложений для ПК с использованием технологии WPF. Тут то уже намного дольше удалось поработать, до сих пор делаю небольшие приложения (позже напишу про процесс их создания). Мне она понравилась за счёт возможности быстро и легко создавать красивый адаптивный интерфейс, использовать архитектурный паттерн MVVM и достаточно удобно работать с графикой. Но все же душа у меня тянулась к Веб разработке...</p>
  <p>Во время другой командировки мне удалось изучить HTML, CSS, JavaScript для создания frontend, а так же начал читать про возможности Asp.Net MVC. Параллельно с обучением работал над проектом по созданию движка для блога, поэтому все что удавалось узнать сразу применял на практике. Очень хотелось доделать его до конца и на его основе вести свой блог)) но так до конца не получилось его реализовать, времени не хватило... Хотя сейчас я бы его уже делал совсем по другому. Наверное в скором времени переделаю и выложу здесь описание.</p>
  <p>Так же в рамках открытого мастер-класса сделал проект телеграмм бота на C#. По запросу города он отправлял погоду, которую подгружал с сервисов. Тоже интересный проект, нужно будет ещё парочку ботов сделать)</p>
  <p>В целом мне удалось попробовать много технологий. Больше всего понравилась web backend разработка, в направлении которой и дальше буду развиваться. В течение 2019 года хочу сменить работу и уйти в программисты про этот путь и буду вам рассказывать.</p>
  <p>В блоге буду делиться своим опытом, мыслями, проектами, интересными статьями и всякими полезными штуками.</p>
  <p>Я рад новым знакомствам и общению, пишите отзывы здесь или обращайтесь напрямую в телеграмм (<a href="http://t.me/vncntbrown" target="_blank">мой аккаунт</a>). Так же можете подписаться на наш канал в телеграмме - <a href="http://t.me/maddevelop" target="_blank">Mad Develop</a>, там мы выкладываем статьи, шутейки и краткие истории.</p>
  <p>Контакты:</p>
  <ol>
    <li><a href="http://t.me/vncntbrown" target="_blank">Telegram</a></li>
    <li><a href="https://github.com/NiGushch" target="_blank">GitHub</a></li>
    <li><a href="https://www.linkedin.com/in/николай-гущарин-8488b4159/" target="_blank">LinkedIn</a></li>
    <li><a href="https://moikrug.ru/nigushch" target="_blank">Мой круг</a></li>
  </ol>

]]></content:encoded></item></channel></rss>