<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Несерьезный шарпист</title><subtitle>Рефлексируем о .NET под пивом с ц#</subtitle><author><name>Несерьезный шарпист</name></author><id>https://teletype.in/atom/notserious_seesharp</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/notserious_seesharp?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@notserious_seesharp?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=notserious_seesharp"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/notserious_seesharp?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-05-13T23:20:51.052Z</updated><entry><id>notserious_seesharp:inheritance</id><link rel="alternate" type="text/html" href="https://teletype.in/@notserious_seesharp/inheritance?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=notserious_seesharp"></link><title>Наследование, абстрактные классы и интерфейсы</title><published>2024-07-05T09:56:48.656Z</published><updated>2025-01-04T20:58:49.975Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/ca/70/ca70f82d-6917-4365-8592-1544f3349779.png"></media:thumbnail><category term="oop" label="ООП"></category><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/04/77/04774c12-8cc7-4049-8dcc-c0c16b5d8253.png&quot;&gt;Из предыдущей лекции мы уже говорили, что наследование - это одна из парадигм ооп, механизм установления специального отношения между классами для упрощения переиспользования кода.</summary><content type="html">
  &lt;figure id=&quot;fEh4&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/04/77/04774c12-8cc7-4049-8dcc-c0c16b5d8253.png&quot; width=&quot;816&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;pUzF&quot;&gt;Парадигма наследования&lt;/h3&gt;
  &lt;p id=&quot;IfQe&quot;&gt;В &lt;a href=&quot;https://teletype.in/@notserious_seesharp/base-concepts-oop&quot; target=&quot;_blank&quot;&gt;предыдущей лекции&lt;/a&gt; мы уже говорили, что наследование - это одна из парадигм ооп, механизм установления специального отношения между классами для упрощения переиспользования кода.&lt;/p&gt;
  &lt;p id=&quot;sr8l&quot;&gt;Наследование основывается на двух понятиях базового и производного класса:&lt;/p&gt;
  &lt;ul id=&quot;swAx&quot;&gt;
    &lt;li id=&quot;uv8X&quot;&gt;Базовый класс (родительский) - класс, от которого поля и методы наследуются другим классом.&lt;/li&gt;
    &lt;li id=&quot;Suv4&quot;&gt;Производный класс (дочерний) — класс, унаследованный от базового класса.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;Whpu&quot;&gt;В качестве примера создадим некий базовый класс транспортного средства &lt;code&gt;Vehicle&lt;/code&gt;, который бы обладал рядом характеристик и методов работы с ними, присущих всем транспортным средствам. И унаследуем от &lt;code&gt;Vehicle&lt;/code&gt; класс &lt;code&gt;Car&lt;/code&gt;, который будет наследовать все открытые атрибуты и методы.&lt;/p&gt;
  &lt;figure id=&quot;HEuQ&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/9c/81/9c81f077-2630-4c94-bf5d-402d9600f06a.png&quot; width=&quot;813&quot; /&gt;
    &lt;figcaption&gt;Пример наследования класса Car от базового класса Vehicle&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;pre id=&quot;dkjE&quot; data-lang=&quot;clike&quot;&gt;public class Vehicle
{
    public int MaxSpeed { get; set; }
    public int YearOfIssue { get; set; }
     
    public void Move() 
    {
        Console.WriteLine(&amp;quot;I&amp;#x27;m moving&amp;quot;)
    }
}

public class Car : Vehicle
{
    public int CountWeels { get; set; }
    public int HpEngine { get; set; }
}

public class Program
{
    public static void Main()
    {
        var bmw = new Car 
        {
            MaxSpeed = 500,
            HpEngine = 1000,
            YearOfIssue = 1,
            CountWeels = 4,
        };
        
        bmw.Move();
    }
}&lt;/pre&gt;
  &lt;p id=&quot;0Bwf&quot;&gt;Таким образом, используя наследование нам не нужно создавать один и тот же код в разных классах, мы используем уже написанное и не нарушаем принцип разработки ПО DRY (dont repeat yourself).&lt;/p&gt;
  &lt;p id=&quot;pEWL&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;79mQ&quot;&gt;Многоуровневая цепочка наследования&lt;/h3&gt;
  &lt;p id=&quot;WI7f&quot;&gt;Естественно мы можем сделать и многоуровневое наследование, когда один класс является производным от другого, который в свою очередь также является наследником. &lt;/p&gt;
  &lt;p id=&quot;jbqy&quot;&gt;Мы можем добавить в наш пример класс грузовика Truck, который бы наследовался от Car и имел некую грузоподъемность прицепа, это будет являться многоуровневым наследованием.&lt;/p&gt;
  &lt;figure id=&quot;bv3R&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/8e/f4/8ef43c0a-2461-4cd0-86e9-cce6201c23dc.png&quot; width=&quot;963&quot; /&gt;
  &lt;/figure&gt;
  &lt;pre id=&quot;rmQR&quot; data-lang=&quot;clike&quot;&gt;public class Vehicle
{
    public int MaxSpeed { get; set; }
    public int YearOfIssue { get; set; }
     
    public void Move() 
    {
        Console.WriteLine(&amp;quot;I&amp;#x27;m moving&amp;quot;)
    }
}

public class Car : Vehicle
{
    public int CountWeels { get; set; }
    public int HpEngine { get; set; }
}

public class Truck : Car
{
    public int TrailerCapacity { get; set; }
}&lt;/pre&gt;
  &lt;p id=&quot;B5eO&quot;&gt;Также стоит помнить, что все классы неявно наследуются от базового класса &lt;code&gt;Object&lt;/code&gt;, от него все классы и наследуют базовые методы &lt;code&gt;ToString()&lt;/code&gt;, &lt;code&gt;GetHashCode()&lt;/code&gt;, &lt;code&gt;GetType()&lt;/code&gt;, &lt;code&gt;Equals()&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;SEmX&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;j23m&quot;&gt;Доступ к членам базового класса&lt;/h3&gt;
  &lt;p id=&quot;lTP6&quot;&gt;При наследовании класс наследник получает доступ к членам базового класса в зависимости от их спецификатора доступа (о них мы уже говорили в статье про классы &lt;a href=&quot;https://teletype.in/@notserious_seesharp/classes_basic#Xll7&quot; target=&quot;_blank&quot;&gt;туть&lt;/a&gt;).&lt;/p&gt;
  &lt;p id=&quot;nCgn&quot;&gt;Если кратко, то класс наследник может иметь доступ только к членам базового класса, которые определены с модификаторами &lt;code&gt;public&lt;/code&gt;, &lt;code&gt;protected&lt;/code&gt;, &lt;code&gt;internal&lt;/code&gt; (доступ в любом месте кода той же сборки), &lt;code&gt;private protected&lt;/code&gt; (доступ в базовом классе и классах наследниках в той же сборке), &lt;code&gt;protected internal&lt;/code&gt; (доступ в той же сборке и в классах наследниках любой сборки). К членам со спецификатором &lt;code&gt;private&lt;/code&gt; можно получить доступ только в том же классе, обратиться к ним из классов наследников не получится.&lt;/p&gt;
  &lt;pre id=&quot;ZRLw&quot; data-lang=&quot;clike&quot;&gt;public class Robot
{
    private int beerCount;

    public void Drink() 
    {
        Console.WriteLine($&amp;quot;bimbimbambam, I can have a drink {beerCount} beer bottles&amp;quot;);
    }
}

public class Bender : Robot
{
    public void SomeMethod()
    {
        // Доступ к private полю beerCount из производного класса запрещен
        // beerCount = 10; Error
        Drink();  // Вызов Drink из производного класса
    }
}&lt;/pre&gt;
  &lt;p id=&quot;y7vE&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;lhsh&quot;&gt;Вызов конструктора базового класса&lt;/h3&gt;
  &lt;p id=&quot;pCHK&quot;&gt;Если базовый класс не имеет конструктора по умолчанию, то необходимо явно вызвать один из конструкторов базового класса из конструктора дочернего класса с помощью ключевого слова &lt;code&gt;base&lt;/code&gt;. Если же этого не сделать, то компилятор автоматически неявно попытается вызвать конструктор по умолчанию базового класса, и если же такой конструктор в данной ситуации будет отсутствовать, то возникнет ошибка компиляции.&lt;/p&gt;
  &lt;p id=&quot;rCG9&quot;&gt;В примере выше все ок, и при создании объекта класса Bender будет вызван его конструктор по умолчанию и конструктор по умолчанию базового класса Robot (при чем в порядке наследования, начиная от базового). Но давайте смоделируем ситуацию, когда у Robot нет конструктора по умолчанию.&lt;/p&gt;
  &lt;pre id=&quot;izkb&quot; data-lang=&quot;clike&quot;&gt;public class Robot
{
    private int intelligenceLevel;
    
    public Robot(int intelligenceLevel)
    {
        this.intelligenceLevel = intelligenceLevel;
    }
}

public class Bender : Robot
{
    private int beerCount;
    
    public Bender(int intelligenceLevel, int beerCount)
        : base(intelligenceLevel)
    {
        this.beerCount = beerCount;
    }

    public void Drink() 
    {
        Console.WriteLine($&amp;quot;bimbimbambam, I can have a drink {beerCount} beer bottles&amp;quot;);
    }
}&lt;/pre&gt;
  &lt;p id=&quot;UUQA&quot;&gt;В примере мы сделали некий класс &lt;code&gt;Robot&lt;/code&gt;, у которого есть единственный конструктор, принимающий параметр некий уровень интеллекта &lt;code&gt;intelligenceLevel&lt;/code&gt;, то есть конструктора по умолчанию у него больше нет (в таком случае мы можем явно прописать конструктор без параметров). Если при наследовании в классе &lt;code&gt;Bender&lt;/code&gt; не вызвать данный конструктор базового класса через &lt;code&gt;base&lt;/code&gt;, как показано, передав в него необходимые аргументы, то будет ошибка компиляции (то есть в унаследованных классах от &lt;code&gt;Robot&lt;/code&gt; обязательно необходимо явно прописывать конструктор даже если он без параметров, чтобы вызывать конструктор базового класса и передать в него некоторые значения).&lt;/p&gt;
  &lt;p id=&quot;Btqu&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;wsTy&quot;&gt;Порядок вызова конструкторов и финализаторов в цепочке наследования&lt;/h3&gt;
  &lt;p id=&quot;KIgb&quot;&gt;При создании объекта класса сначала отрабатывают конструкторы базовых классов и только затем конструкторы производных (начиная с &lt;code&gt;Object&lt;/code&gt;). При финализации объекта класса процесс происходит в обратном порядке.&lt;/p&gt;
  &lt;pre id=&quot;7dPe&quot; data-lang=&quot;clike&quot;&gt;public class Robot
{
    public Robot()
    {
        Console.WriteLine(&amp;quot;Robot created&amp;quot;);
    }
    
    ~Robot()
    {
        Console.WriteLine(&amp;quot;Robot finalized&amp;quot;);
    }
}

public class Bender : Robot
{
    public Bender()
    {
        Console.WriteLine(&amp;quot;bimbimbambam, Bender created&amp;quot;);
    }
    
    ~Bender()
    {
        Console.WriteLine(&amp;quot;bambambimbim, Bender finalized&amp;quot;);
    }
}

public class Program
{
    public static void Temp()
    {
        var bender = new Bender();
    }

    public static void Main()
    {

        Temp();
        GC.Collect();
    }
}

// Вывод:
// Robot created
// bimbimbambam, Bender created
// bambambimbim, Bender finalized
// Robot finalized&lt;/pre&gt;
  &lt;p id=&quot;776q&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;w7PW&quot;&gt;Запрет наследования&lt;/h3&gt;
  &lt;p id=&quot;kTfu&quot;&gt;Когда класс предназначен только для использования внутри определенного контекста или библиотеки и не предназначен для расширения или изменения, его наследование может быть запрещено для предотвращения нежелательных изменений и расширений в классах наследниках.&lt;/p&gt;
  &lt;p id=&quot;8wq4&quot;&gt;Для этих целей используется ключевое слово &lt;code&gt;selead&lt;/code&gt;&lt;/p&gt;
  &lt;pre id=&quot;Bae9&quot; data-lang=&quot;clike&quot;&gt;// от класса робот наследование запрещено
public sealed class Robot
{
}

// ошибка компиляции 
public class Bender : Robot
{
}&lt;/pre&gt;
  &lt;p id=&quot;6bpC&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;OyfU&quot;&gt;Переопределение методов&lt;/h3&gt;
  &lt;p id=&quot;0p07&quot;&gt;При наследовании часто возникает необходимость в изменении реализации метода в классе-наследнике. Для этого используется переопределение методов. На этом основывается парадигма полиморфизма, о чем мы еще успеем поговорить в следующей статье. Сейчас же важно понять простую истину, что переопределение метода позволяет дочернему классу предоставить новую реализацию для метода, который уже определен в базовом классе. &lt;/p&gt;
  &lt;p id=&quot;NJNT&quot;&gt;Метод, который мы хотим сделать доступным для переопределения, в базовом классе помечаем ключевым словом &lt;code&gt;virtual&lt;/code&gt;.  А чтобы переопределить метод в классе наследнике, используем ключевое слово &lt;code&gt;override&lt;/code&gt;. Андерстенд, ок?!&lt;/p&gt;
  &lt;pre id=&quot;qzEi&quot; data-lang=&quot;clike&quot;&gt;public class Robot
{
    public virtual void Drink()
    {
        Console.WriteLine(&amp;quot;I&amp;#x27;m an intelligent robot alcoholic&amp;quot;);
    }
}

public class Bender : Robot
{
    private int beerCount;
    
    public Bender(int beerCount)
    {
        this.beerCount = beerCount;
    }

    public override void Drink() 
    {
        base.Drink(); // вызов метода базового класса
        Console.WriteLine($&amp;quot;bimbimbambam, I can have a drink {beerCount} beer bottles&amp;quot;);
    }
}

public class Program
{
    public static void Main()
    {
        var bender = new Bender(10);
        bender.Drink();
    }
}

// Вывод:
// I&amp;#x27;m an intelligent robot alcoholic
// bimbimbambam, I can have a drink 10 beer bottles&lt;/pre&gt;
  &lt;p id=&quot;jleE&quot;&gt;Кроме того, как показано в примере, в классе наследнике мы можем вызвать непереопределенный метод базового класса, с помощью ключевого слова &lt;code&gt;base&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;HjyF&quot;&gt;Иногда может возникнуть необходимость запретить переопределение только некоторых виртуальных методов в классах наследниках. Мы можем это реализовать также через &lt;code&gt;sealed&lt;/code&gt;, как при запрете наследования.&lt;/p&gt;
  &lt;pre id=&quot;0jzX&quot; data-lang=&quot;clike&quot;&gt;public class Robot
{
    public virtual void Drink()
    {
        Console.WriteLine(&amp;quot;I&amp;#x27;m an intelligent robot alcoholic&amp;quot;);
    }
}

public class Bender : Robot
{
    private int beerCount;
    
    public Bender(int beerCount)
    {
        this.beerCount = beerCount;
    }

    public override sealed void Drink() 
    {
        base.Drink(); // вызов метода базового класса
        Console.WriteLine($&amp;quot;bimbimbambam, I can have a drink {beerCount} beer bottles&amp;quot;);
    }
}

public class SuperBender : Bender
{
    public SuperBender(int beerCount)
        : base(beerCount)
    {
    }
    
    // Ошибка компиляции,переопределять этот метод нельзя
    public override void Drink()
    {
        Console.WriteLine(&amp;quot;wabulabudabda&amp;quot;);
    }
}&lt;/pre&gt;
  &lt;p id=&quot;ugMa&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;fDNj&quot;&gt;Абстрактные классы&lt;/h3&gt;
  &lt;p id=&quot;k20q&quot;&gt;Как уже говорилось ранее класс представляет собой некоторый план для построения определенных объектов (чертеж). Но мы можем выделить в абстракцию некоторые общие черты, свойственные нескольким категориям объектов. Например Троллейбусы и Машины имеют какую-то грузоподъемность, какой-то тип топлива, могут перемещаться, соответственно, можно выделить некоторую абстракцию Транспортное средство, которое само по себе ничего не представляет.&lt;/p&gt;
  &lt;p id=&quot;D5mo&quot;&gt;Таким образом, абстрактный класс - это некий базовый шаблон, который объединяет в себе все основные черты определенной группы объектов и используется для их дальнейшего уточнения, детализации и обособления из общей группы объектов. Но по абстрактному классу не могут создаваться экземпляры.&lt;/p&gt;
  &lt;p id=&quot;uWCC&quot;&gt;Для определения абстрактных классов используется ключевое слово &lt;code&gt;abstarct&lt;/code&gt;. Они также могут иметь переменные, методы, свойства, конструкторы (несмотря на то, что объекты данных классов создавать напрямую нельзя). Помимо обычных методов и свойств в абстрактных классах объявлять абстрактные члены, не предоставляя конкретной реализации, которая должна (пере)определяться в классах наследниках.&lt;/p&gt;
  &lt;pre id=&quot;cVRI&quot; data-lang=&quot;clike&quot;&gt;public abstract class Vehicle
{
    public int Carrying { get; set; }
    
    public abstract void Move();
}&lt;/pre&gt;
  &lt;h3 id=&quot;H6ZV&quot;&gt;&lt;/h3&gt;
  &lt;p id=&quot;Ybda&quot;&gt;Любой класс, который унаследован от абстрактного класса, должен реализовывать все его абстрактные члены.&lt;/p&gt;
  &lt;pre id=&quot;4YZX&quot; data-lang=&quot;clike&quot;&gt;public class Car : Vehicle
{
    public override void Move()
    {
        Console.WriteLine(&amp;quot;Машина едет со скоростью n&amp;quot;);
    }
}

public class Trolleybus
{
    public override void Move()
    {
        Console.WriteLine(&amp;quot;Тролейбус едет со скоростью x&amp;quot;);
    }
}&lt;/pre&gt;
  &lt;p id=&quot;sOZu&quot;&gt;Но в классе наследнике можно не реализовывать какой-то абстрактный член, но  в таком случае класс наследник становится также абстрактным и должен быть помечен &lt;code&gt;abstract&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;tsTc&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;3PGm&quot;&gt;Интерфейсы&lt;/h3&gt;
  &lt;p id=&quot;EYT7&quot;&gt;Интерфейс можно охарактеризовать как некий протокол, объявляющий некоторый функционал, который способны выполнять реализующие его классы и структуры. Класс имплементирует интерфейс должен должен реализовывать все его методы и свойства.&lt;/p&gt;
  &lt;pre id=&quot;PcS8&quot; data-lang=&quot;clike&quot;&gt;public interface IMovable
{
    void Move();
}

public class Car : IMovable
{
    public override void Move()
    {
        Console.WriteLine(&amp;quot;Машина едет&amp;quot;);
    }
}

public class Ship : IMovable
{
    public override void Move()
    {
        Console.WriteLine(&amp;quot;Корабль идет&amp;quot;);
    }
}&lt;/pre&gt;
  &lt;p id=&quot;HzVW&quot;&gt;Начиная с C# 8, интерфейсы поддерживают реализацию методов по умолчанию, которая может переопределяться или не переопределяться в классе реализации. Помимо этого с C# 8 для интерфейсов стало доступно определение у членов спецификатора доступа (по умолчанию - &lt;code&gt;public&lt;/code&gt;, в отличии от классов), константы, статические поля, статические методы и свойства, статические ивенты и индексаторы, статические конструкторы.&lt;/p&gt;
  &lt;pre id=&quot;v1X4&quot; data-lang=&quot;clike&quot;&gt;public interface IMovable
{
    void Move()
    {
        Console.WriteLine(&amp;quot;Я могу перемещаться&amp;quot;);
    }
}

public class Car : IMovable
{
    public override void Move()
    {
        Console.WriteLine(&amp;quot;Машина едет&amp;quot;);
    }
}

public class Bender : IMovable
{
}&lt;/pre&gt;
  &lt;p id=&quot;1fTI&quot;&gt;При реализации по умолчанию в интерфейсе мы не обязаны реализовывать этот член в реализующем классе, мы не получим ошибку. &lt;/p&gt;
  &lt;p id=&quot;UxT8&quot;&gt;Но важно знать, что интерфейс в отличии от абстрактного класса не позволяет полноценно переиспользовать уже написанный код. То есть если мы создадим объект класса &lt;code&gt;Bender&lt;/code&gt; из примера выше и попытаемся вызвать метод &lt;code&gt;Move&lt;/code&gt;, то получим ошибку, т.к. класс не определяет данный метод:) &lt;em&gt;Но как же тогда реализация по умолчанию, зачем она нужна?! Э&lt;/em&gt;то хорошо работает с полиморфизмом, о чем мы поговорим в следующей статье, пока просто можно принять тот факт, что ссылка на базовый класс может хранить объекты всей цепочки классов наследников, расширяющих данный базовый класс (такая же логика работает и со ссылкой на интерфейс). &lt;/p&gt;
  &lt;pre id=&quot;Y9MO&quot; data-lang=&quot;clike&quot;&gt;public class Program
{
    public static void Main()
    {
        // Bender bender = new Bender();
        // bender.Move(); // ошибка компиляции!!!
        
        IMovable bender2 = new Bender();
        bender2.Move(); // Я могу перемещаться
    }
}&lt;/pre&gt;
  &lt;p id=&quot;DBzA&quot;&gt;Интерфейс по прежнему не может иметь обычный конструктор по той причине, что по нему нельзя создать никакой объект, это по прежнему &amp;quot;протокол&amp;quot; для классов, который не может иметь никакого внутреннего состояния.&lt;/p&gt;
  &lt;p id=&quot;PsSj&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;zomG&quot;&gt;Множественное наследование&lt;/h3&gt;
  &lt;p id=&quot;JkKC&quot;&gt;В отличие от всяких мерзких языков типа C++, которые лучше трогать только палкой, множественное наследование в C# отсутствует в классическом виде из-за ряда проблем, главной из которых является ошибка проектирования так называемый &amp;quot;алмаз смерти&amp;quot;. &lt;/p&gt;
  &lt;p id=&quot;GoNc&quot;&gt;Алмаз смерти - это ситуация, когда один класс наследует функциональность от двух классов, которые в свою очередь являются потомками одного и того же базового класса. Данная ситуация приводит к неоднозначности и конфликту в иерархии классов.&lt;/p&gt;
  &lt;figure id=&quot;RE1X&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c8/2f/c82f1d62-0a14-4154-82ef-81226fd77c54.png&quot; width=&quot;220&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;MF8s&quot;&gt;Таким образом, при создании объекта класса D по логике в памяти будет создано аж 2 объекта базового класса A, и в D будет унаследованы переопределения его методов и от B и от C, что является полной шизофренией.&lt;/p&gt;
  &lt;p id=&quot;5OKO&quot;&gt;В C# класс может наследоваться только от одного базового класса, что исключает описанную выше ситуацию. Но тем не менее классы и структуры могут реализовывать множество интерфейсов по той причине, что в памяти никакого объекта интерфейса не создается, а следовательно можно и реализовать множество &amp;quot;протоколов&amp;quot;.&lt;/p&gt;
  &lt;pre id=&quot;xDUP&quot; data-lang=&quot;clike&quot;&gt;public interface IMovable
{
    void Move()
    {
        Console.WriteLine(&amp;quot;Я могу перемещаться&amp;quot;);
    }
}

public interface IDrinker
{
    void Drink();
}

public class Bender : IMovable, IDrinker
{
    public override void Move()
    {
        Console.WriteLine(&amp;quot;Я куда-то иду&amp;quot;);
    }
    
    public override void Drink()
    {
        Console.WriteLine(&amp;quot;бимбимбамбам, пива много не бывает&amp;quot;);
    } 
}&lt;/pre&gt;
  &lt;p id=&quot;q4zT&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;c9t9&quot;&gt;Разница между абстрактными классами и интерфейсами&lt;/h3&gt;
  &lt;p id=&quot;kavd&quot;&gt;Давайте подведем итоги по основным отличиям абстрактных классов от интерфейсов и определим, когда что лучше использовать, потому что это не совсем очевидно, особенно после нововведений в C# 8 (нововведения... сейчас C# 12 уже, мда, в плюсах такой проблемы нет:))).&lt;/p&gt;
  &lt;p id=&quot;ppAa&quot;&gt;Отличия интерфейсов и абстрактных классов:&lt;/p&gt;
  &lt;ul id=&quot;F2jC&quot;&gt;
    &lt;li id=&quot;7jb7&quot;&gt;В абстрактных классах, как и в обычных классах, спецификатор доступа к членам по умолчанию - &lt;code&gt;private&lt;/code&gt;, а в интерфейсах - &lt;code&gt;public&lt;/code&gt;.&lt;/li&gt;
    &lt;li id=&quot;wK3k&quot;&gt;Интерфейсы в отличие от абстрактных классов могут иметь только статические конструкторы, т.к. в отличие от абстрактных классах в памяти не будет создаваться объект и у интерфейса нет никакого состояния, которое создается в конструкторе.&lt;/li&gt;
    &lt;li id=&quot;xg9y&quot;&gt;Возможность переиспользования кода в интерфейсах весьма ограничена, т.к. реализация по умолчанию доступна для вызова только от лица интерфейса.&lt;/li&gt;
    &lt;li id=&quot;gfeI&quot;&gt;Интерфейс может расширять множество других интерфейсов и классы могут реализовывать также более одного интерфейса. С абстрактным классом логика наследования такая же, как и с обычными классами, то есть множественное наследование от абстрактных классов запрещено.&lt;/li&gt;
    &lt;li id=&quot;qvaB&quot;&gt;Структуры могут реализовывать интерфейсы, но не могут наследоваться от абстрактных классов.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;NRHJ&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Q8h8&quot;&gt;Таким образом, используем интерфейс, если:&lt;/p&gt;
  &lt;ul id=&quot;2HOL&quot;&gt;
    &lt;li id=&quot;uzzj&quot;&gt;Необходимо определить общий функционал (протокол) для различных классов объектов, которые слабо связаны между собой. (IClonable, IDisposable, IEnumerable, все эти базовые интерфейсы определяют некоторый функционал/контракт/протокол, которому соответствуют реализующие их классы)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;IRqO&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;vYWk&quot;&gt;Используем абстрактные классы, если:&lt;/p&gt;
  &lt;ul id=&quot;HwON&quot;&gt;
    &lt;li id=&quot;ILvv&quot;&gt;Нам необходимо разработать общий функционал для родственных объектов, как некую абстрактную сущность (которую можно в отличие от интерфейса IClonable представить, например, Vehicle).&lt;/li&gt;
    &lt;li id=&quot;cNf4&quot;&gt;Классы наследники нуждаются в большом количестве общей функциональной логики или её предполагается часто менять. При этом достаточно изменить логику в классе родителе, чтобы она изменилась во всех классах наследниках.&lt;/li&gt;
    &lt;li id=&quot;3Jhk&quot;&gt;Если класс сам по себе не является полноценной единицей, с которой можно взаимодействовать, то используем абстрактный класс, а не обычный.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;PBnX&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;pwws&quot;&gt;Ну, вот, надеюсь, стали понятны различия интерфейсов и абстрактных классов, а то многие джуны вообще не выкупают, что когда использовать.&lt;/p&gt;
  &lt;p id=&quot;jEGM&quot;&gt;Подписывайтесь на мой &lt;a href=&quot;https://t.me/serious_seesharp&quot; target=&quot;_blank&quot;&gt;канал в тг&lt;/a&gt;, там говорят правду! В следующей статье раскроем тему полиморфизма.&lt;/p&gt;

</content></entry><entry><id>notserious_seesharp:base-concepts-oop</id><link rel="alternate" type="text/html" href="https://teletype.in/@notserious_seesharp/base-concepts-oop?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=notserious_seesharp"></link><title>Основы принципов ООП</title><published>2023-07-12T00:58:21.321Z</published><updated>2024-01-05T19:07:47.566Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/28/5e/285eb7b5-4d3f-44a6-9470-99ca14262908.png"></media:thumbnail><category term="oop" label="ООП"></category><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/81/f3/81f334cd-b844-4c1e-8e40-646b879c9729.png&quot;&gt;Здорово! В данной статье ты сможешь познакомиться основными понятиями объектно-ориентированного программированием (ООП) и его парадигмами, узнать откуда данная штука взялась, и какие еще существуют методологии программирования.</summary><content type="html">
  &lt;figure id=&quot;Wm0U&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/81/f3/81f334cd-b844-4c1e-8e40-646b879c9729.png&quot; width=&quot;1200&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Ye66&quot;&gt;Здорово! В данной статье ты сможешь познакомиться основными понятиями объектно-ориентированного программированием (ООП) и его парадигмами, узнать откуда данная штука взялась, и какие еще существуют методологии программирования.&lt;/p&gt;
  &lt;h3 id=&quot;8Siw&quot;&gt;Подходы программирования, история ООП&lt;/h3&gt;
  &lt;p id=&quot;9Brd&quot;&gt;Для того, чтобы полноценно разобраться в ООП и понимать принципиальную разницу между различными концепциями программирования, начнем с самого начала и разберемся с понятиями императивного и декларативного подходов.&lt;/p&gt;
  &lt;p id=&quot;SHRP&quot;&gt;В 1930-х годах перед математиками встала &lt;a href=&quot;http://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D0%B1%D0%BB%D0%B5%D0%BC%D0%B0_%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F&quot; target=&quot;_blank&quot;&gt;проблема разрешения&lt;/a&gt;, сформулированная Давидом Гильбертом. Суть её в том, что необходимо найти алгоритм, который бы за конечное число шагов находил ответ на утверждение, задаваемое на формальном языке, истинно или ложно. Задача была опровергнута с помощью двух подходов, разработанных математиками Алонзо Чёрчем и Аланом Тьюрингом. Они показали (первый — с помощью изобретённого им λ-исчисления, а второй — теории машины Тьюринга), что для арифметики такого алгоритма не существует в принципе, т.е. данная задача в общем случае неразрешима.&lt;/p&gt;
  &lt;p id=&quot;CmvD&quot;&gt;Лямбда-исчисление представляет собой формальный аппарат для выражения вычислений, в основе которого лежит лямбда-функция (анонимная функция), и способного определить в своих терминах любую языковую конструкцию или алгоритм. Лямбда-исчисление оказало огромное влияние на развитие теории языков программирования и лежит в основе декларативного подхода программирования.&lt;/p&gt;
  &lt;p id=&quot;Fh14&quot;&gt;Декларативный подход заключается в указании требуемого результата, который нужно достичь, а не конкретных шагов по его достижению. В декларативном стиле программист описывает желаемый результат и оставляет системе определение оптимального способа его достижения. Это позволяет абстрагироваться от деталей реализации и сосредоточиться на логике задачи. Декларативный подход в свою очередь делится на два подмножества: функциональное программирование и логическое.&lt;/p&gt;
  &lt;p id=&quot;mmah&quot;&gt;&lt;strong&gt;Функциональное программирование&lt;/strong&gt; основывается на лямбда-исчислении и строится на совокупности определений функций, которые не влияют на глобальный контекст всей программы. Задача программиста при таком подходе - описать взаимодействие между данными функциями на уровне всей программы. При чисто функциональном программировании переменные в программе являются константами, получив однажды свое значение из результата вычисления функции, они больше не меняются. Отсутствие побочных эффектов в процессе вычисления функций приводит к тому, что порядок выполнения отдельных фрагментов программы не существенен - итоговое значение в любом случае будет одинаковым. В качестве примера чисто функционального языка можно привести Haskell.&lt;/p&gt;
  &lt;p id=&quot;CxXX&quot;&gt;&lt;strong&gt;В логическом программировании &lt;/strong&gt;программы выражены в виде формул математической логики и компьютер для решения задачи пытается вывести логические следствия из них. Примером может послужить Prolog (как-то пробовал делать одну штуку на нем... такая срань, вы не поверите, лучше притвориться, что его не существует).&lt;/p&gt;
  &lt;p id=&quot;EacZ&quot;&gt;При императивном подходе программист явно указывает компьютеру, какие шаги нужно выполнить для достижения желаемого результата, и основывается на машине Тьюринга.&lt;/p&gt;
  &lt;blockquote id=&quot;c3NF&quot;&gt;Бесконечная лента разбита на площадки помеченные символами. В каждый момент машине доступен ровно один символ. Состояние машины зависит от доступного символа и машина вольна этот символ изменять. Недоступные символы на ленте никак не влияют на поведение машины. Однако ленту можно двигать туда-сюда, это — элементарная для машины операция. Таким образом шанс есть у любого символа.&lt;/blockquote&gt;
  &lt;p id=&quot;p8ME&quot;&gt;Лямбда-исчисление и машины Тьюринга функционально эквивалентны — что все, что можно вычислить с помощью машины Тьюринга, можно вычислить с помощью лямбда-исчисления, и наоборот, исходя из тезиса Чёрча-Тьюринга. То есть одну и ту же задачу можно выполнить разными подходами, но где-то нам данную задачу будет реализовать проще.&lt;/p&gt;
  &lt;p id=&quot;q4xI&quot;&gt;Одним из первых языков программирования, основанных на императивном подходе, был низкоуровневый язык ассемблера, появившийся в конце 40-х, дающий полный доступ к аппаратным средствам компьютера. Следующий шаг в развитии императивного подхода произошел к концу 50-х с появлением &amp;quot;высокоуровневых&amp;quot; языков, таких как Fortran, COBOL и ALGOL, которые стали предоставлять более абстрактные языковые конструкции для написания кода. И уже позднее в 60-х годах в результате развития императивного подхода  берет свое начало ООП.&lt;/p&gt;
  &lt;p id=&quot;gMbV&quot;&gt;В 1961 году в Норвегии Оле-Йохан Даль и Кристен Найгаард начали совместную работу над созданием языка описание для компьютерного моделирования взрывающихся кораблей. Они поняли, что могут группировать корабли по разным категориям. У каждого типа корабля будет свой собственный класс, и класс будет иметь свое уникальное поведение и данные. Simula отвечала не только за введение концепции класса, но также за введение экземпляра класса.&lt;/p&gt;
  &lt;p id=&quot;fgjH&quot;&gt;В результате работы после нескольких версий появился язык SIMULA 67, завершенный в 1967 году. В язык были заложены принципиально новые механизмы программирования и структурирования кода по сравнению с существующими языками. Для работы с классами были введены такие концепции, как инкапсуляция, динамическое и статическое создание экземпляров, сокрытие данных.&lt;/p&gt;
  &lt;p id=&quot;39j2&quot;&gt;Термин «объектно-ориентированное программирование» ввел Алан Кей при работе на компанию Xerox PARC над своим языком программирования Smalltalk. Этот термин использовался для обозначения процесса использования объектов в качестве основы для вычислений. Smalltalk был вдохновлен идеями, заложенными в Simula 67, но Smalltalk был разработан таким образом, чтобы он был динамичным. Объекты можно было изменять, создавать или удалять, и это отличалось от обычно используемых статических систем. А также Smalltalk был первым языком программирования, в котором была представлена ​​концепция наследования.&lt;/p&gt;
  &lt;p id=&quot;xkh7&quot;&gt;Повсеместное распространение и всеобщее признание ООП получило с дальнейшей реализацией идей SIMULA в таких мастодонтах, как C++ и Java. Сейчас же ООП является наиболее важным подходом и в процентах 95 в продуктовой разработке используется именно он (и только всякие глупые олимпиадники пытаются протолкнуть свою фпшку, код которой будет работать на 0.00005 наносекунды быстрее🤡).&lt;/p&gt;
  &lt;p id=&quot;qDLn&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;yV4I&quot;&gt;ООП&lt;/h3&gt;
  &lt;p id=&quot;hhCd&quot;&gt;Объектно-ориентированное программирование - это подход программирования, при котором программа представляется, как совокупность объектов, имеющих свое состояние и поведение и взаимодействующих друг с другом. Таким образом, разработчик фокусируется на описании реальных объектов и данных, а не функций и логики. &lt;/p&gt;
  &lt;p id=&quot;f9AJ&quot;&gt;И это значительно упрощает понимание структурное устройство кода. Ведь с точки зрения отображения на реальный мир все, что крякает как утка и выглядит как утка, будет являться уткой, то есть объектом, у которого есть набор данных (две лапы, клюв) и он умеет делать набор действий (плавать, крякать).&lt;/p&gt;
  &lt;figure id=&quot;7QnI&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSFhg7MQwD8gUIj6SiMgfCvVQrimTNxU8aVhQ&amp;usqp=CAU&quot; width=&quot;268&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ENnW&quot;&gt;Итак, классы и объекты - основа ООП, но между ними есть принципиальная разница. Класс - это некий шаблон, по которому будут создаваться объекты, и который определяет набор данных и методов для работы с ними. Объект - это экземпляр класса, отдельно созданная единица. Класс - абстракция, а объекты - реальные сущности.&lt;/p&gt;
  &lt;figure id=&quot;hWqF&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/be/43/be439cb3-3b38-4f70-b06b-e78f94028ca7.png&quot; width=&quot;1200&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;XWTa&quot;&gt;По тому, как устроена работа с классами в C# (создание объектов, очистка памяти, обращение к методам, статические классы, спецификаторы доступа и пр.), повторяться не будем, это все можно найти в предыдущих лекциях курса по основам. Если не читали и не понимаете о чем речь, прочитайте, там все подробно разложено. А сейчас сконцентрируемся на парадигмах ООП: абстракция, инкапсуляция, наследование и полиморфизм.&lt;/p&gt;
  &lt;h3 id=&quot;NoLX&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;trGO&quot;&gt;Абстракция&lt;/h3&gt;
  &lt;p id=&quot;OIRl&quot;&gt;Абстракция - набор значимых характеристик определенной категории объектов. Абстрагирование - это выявление свойств, связей и зависимостей для объекта, с целью выделить определенные закономерности, отбросив незначимые характеристики и скрыв детали реализации. Результатом абстрагирования являются абстрактные понятия (абстракции).&lt;/p&gt;
  &lt;p id=&quot;2ilF&quot;&gt;Абстракция в ООП подразумевает создание лаконичных классов, отвечающих только своему необходимому набору свойств.&lt;/p&gt;
  &lt;p id=&quot;niRd&quot;&gt;Например, возьмем класс телефон. У него будет кнопка включения, метод для включения/выключения и метод, позволяющий кому-то звонить, есть телефон включен.&lt;/p&gt;
  &lt;p id=&quot;RNIl&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;42kG&quot;&gt;Инкапсуляция&lt;/h3&gt;
  &lt;p id=&quot;ylGB&quot;&gt;Инкапсуляция отвечает за связь данных объекта с методами работы с ними, таким образом, объект должен контролировать свое внутреннее состояние. Одним из свойств инкапсуляции является сокрытие данных.&lt;/p&gt;
  &lt;p id=&quot;MgdW&quot;&gt;Таким образом, поля класса должны быть скрыты модификатором private, а для доступа к ним должны использоваться публичные методы интерфейса гетеры и сеттеры (подробнее про них &lt;a href=&quot;https://teletype.in/@notserious_seesharp/classes_objects&quot; target=&quot;_blank&quot;&gt;тут&lt;/a&gt;), которые позволяют задать дополнительную валидацию данных для установки и получения определенных значений. Это повышает гибкость класса и гарантирует инвариантность объектов.&lt;/p&gt;
  &lt;p id=&quot;nXh1&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;wZgU&quot;&gt;Наследование&lt;/h3&gt;
  &lt;p id=&quot;l3IY&quot;&gt;Наследование - установление специальных отношений между двумя и более классами, в результате которых класс наследник включает в себя свойства класса родителя. Что позволяет позволяет переиспользовать уже написанный код ранее и делает возможным полиморфизм.&lt;/p&gt;
  &lt;p id=&quot;ekVw&quot;&gt;Например, пусть у нас будет базовый класс &lt;code&gt;Transport&lt;/code&gt;, который будет иметь название и будет уметь перемещаться, а также класс наследник &lt;code&gt;Car&lt;/code&gt;. Тогда данное отношение в C# можно записать через двоеточие у класса наследника следующим образом:&lt;/p&gt;
  &lt;pre id=&quot;mzvi&quot; data-lang=&quot;clike&quot;&gt;public class Transport
{
    public string Name { get; set; }
    
    public void Move()
    {
        Console.WriteLine($&amp;quot;{Name} move&amp;quot;);
    }
}

public class Car : Transport
{
}&lt;/pre&gt;
  &lt;p id=&quot;53ll&quot;&gt;Таким образом, класс наследник Car будет иметь тот же набор полей и методов, которые доступны в соответствии со спецификаторами доступа (про них мы разгоняли &lt;a href=&quot;https://teletype.in/@notserious_seesharp/classes_basic#Xll7&quot; target=&quot;_blank&quot;&gt;тут&lt;/a&gt;)&lt;/p&gt;
  &lt;figure id=&quot;zx1p&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f2/7e/f27e5533-a2bd-4fa7-b113-114c9aab4575.png&quot; width=&quot;870&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Q77y&quot;&gt;По умолчанию все классы наследуются от базового &lt;code&gt;Object&lt;/code&gt;, даже если мы явно не устанавливаем наследование. Поэтому определенные классы &lt;code&gt;Transport&lt;/code&gt; и &lt;code&gt;Car&lt;/code&gt; кроме своих собственных методов, также будут наследовать и методы класса &lt;code&gt;Object&lt;/code&gt;: &lt;code&gt;ToString()&lt;/code&gt;, &lt;code&gt;Equals()&lt;/code&gt;, &lt;code&gt;GetHashCode()&lt;/code&gt; и &lt;code&gt;GetType()&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;eeUL&quot;&gt;Все классы могут наследоваться, кроме помеченных модификатором &lt;code&gt;sealed&lt;/code&gt; (данные классы будут являться как бы конечными в цепочке наследования) и статические классы.&lt;/p&gt;
  &lt;pre id=&quot;n7Rl&quot; data-lang=&quot;clike&quot;&gt;public sealed class Car : Transport
{
}&lt;/pre&gt;
  &lt;p id=&quot;9xVn&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;25do&quot;&gt;Также важно отметить, что конструкторы не передаются производному классу при наследовании. И если в базовом классе не определен конструктор по умолчанию без параметров, а только конструкторы с параметрами, то в производном классе мы обязательно должны вызвать один из этих конструкторов через ключевое слово &lt;code&gt;base&lt;/code&gt;, которое отвечает за доступ в дочернем классе к элементам базового.&lt;/p&gt;
  &lt;pre id=&quot;CAD3&quot; data-lang=&quot;clike&quot;&gt;public class Transport
{
    public Transport(string name)
    {
        Name = name;
    }

    public string Name { get; set; }
    
    public void Move()
    {
        Console.WriteLine($&amp;quot;{Name} move&amp;quot;);
    }
}

public class Car : Transport
{
    public Car(string name, string model)
        : base(name)
    {
        Model = model;
    }
    
    public string Model { get; set; }
}&lt;/pre&gt;
  &lt;p id=&quot;IymE&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;IUyf&quot;&gt;Полиморфизм&lt;/h3&gt;
  &lt;p id=&quot;Xcgl&quot;&gt;Полиморфизм - это способность объектов с одинаковой спецификацией иметь разную реализацию.&lt;/p&gt;
  &lt;p id=&quot;HLxC&quot;&gt;Если усе упрощать, то различают два типа полиморфизма: статический и динамический.&lt;/p&gt;
  &lt;p id=&quot;kvPS&quot;&gt;Статический полиморфизм реализуется через перегрузку методов, достигается за счет того, что имя метода остается прежним, но передаются разные наборы параметров.&lt;/p&gt;
  &lt;pre id=&quot;Iht8&quot; data-lang=&quot;clike&quot;&gt;class Robot
{
    public void SaySomething(string words, int a)
    {
        Console.WriteLine(words);
        Console.WriteLine(a + 3);
    }

    public void SaySomething(string name, string words)
    {
        Console.WriteLine($&amp;quot;{name} - {words}&amp;quot;);
    }
}&lt;/pre&gt;
  &lt;blockquote id=&quot;cwaj&quot;&gt;При вызове перегруженного метода будет искаться первый с наиболее подходящим набором параметров для приведения аргументов.&lt;/blockquote&gt;
  &lt;p id=&quot;J1ju&quot;&gt;Динамический полиморфизм - функция должна иметь возможность обрабатывать единым образом всех наследников какого-то базового типа. Для достижения динамического полиморфизма сигнатура метода, который планируется переопределить, в классе наследнике и в базовом классе должна быть одинаковой. &lt;/p&gt;
  &lt;pre id=&quot;0sOy&quot; data-lang=&quot;clike&quot;&gt;public class Transport
{
    public virtual void Move()
    {
        Console.WriteLine($&amp;quot;Move&amp;quot;);
    }
}

public class Car : Transport
{
}

public class Ship : Transport
{
    public override void Move()
    {
        Console.WriteLine($&amp;quot;Swim&amp;quot;);
    }
}

public class Program
{
    public static void Main()
    {
        SomeFunc(new Transport()); // Move
        SomeFunc(new Car());       // Move
        SomeFunc(new Ship());      // Swim
    }
    
    public static void SomeFunc(Transport transport)
    {
        transport.Move();
    }
}&lt;/pre&gt;
  &lt;p id=&quot;Vegu&quot;&gt;В примере выше у нас есть некоторая функция, которая принимает объект базового класса и вызывает у него виртуальный метод. Мы создаем объекты всех трех представленных классов и передаем в данную функцию, благодаря возможности приведения объектов классов наследников к базовому типа.&lt;/p&gt;
  &lt;p id=&quot;DsZK&quot;&gt;Таким образом, компилятор не знает о переопределяемом методе и выбирает необходимый метод в рантайме по виртуальной таблице методов (но об этом я напишу уже в другой статье/посте, чтобы не затягивать эту:), и мы получаем ожидаемый результат, Сar выводит move, потому что метод не переопределялся, а для Ship вызывается переопределенный метод и выводится swim.&lt;/p&gt;
  &lt;p id=&quot;tCMC&quot;&gt;Также стоит отметить, что есть возможность отменить поведение механизма полиморфизма с помощью модификатора &lt;code&gt;new&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;0Uf2&quot; data-lang=&quot;clike&quot;&gt;public class Transport
{
    public virtual void Move()
    {
        Console.WriteLine($&amp;quot;Move&amp;quot;);
    }
}

public class Ship : Transport
{
    public new void Move()
    {
        Console.WriteLine($&amp;quot;Swim&amp;quot;);
    }
}

public class Program
{
    public static void Main()
    {
        SomeFunc(new Transport()); // Move
        SomeFunc(new Ship());      // Move
    }
    
    public static void SomeFunc(Transport transport)
    {
        transport.Move();
    }
}&lt;/pre&gt;
  &lt;p id=&quot;ns1x&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;HQdU&quot;&gt;А в этой лекции усе. С понятиями познакомились, базу узнали, фп от ооп различать научились... Могу пожелать здоровья психологического при освоении программирования, но это C#, а не C++, так что должно быть все в порядке, главное не сдаваться. В следующей статье поговорим о наследовании, абстрактных классах, интерфейсах, даааа... На канале сейчас возможно постить начну всякие полезности интересные, чтобы он не простаивал пока я большими материалами занимаюсь, так что подписывайтесь 👉&lt;a href=&quot;https://t.me/serious_seesharp&quot; target=&quot;_blank&quot;&gt;https://t.me/serious_seesharp&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>notserious_seesharp:exceptions</id><link rel="alternate" type="text/html" href="https://teletype.in/@notserious_seesharp/exceptions?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=notserious_seesharp"></link><title>Стек вызовов и исключения</title><published>2022-11-30T18:00:35.515Z</published><updated>2024-01-05T19:06:44.637Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/26/49/264986e8-c30e-4c6b-832e-77dedd2c3188.png"></media:thumbnail><category term="osnovy-c" label="Основы C#"></category><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/9f/a0/9fa0d71b-6111-4339-a622-d6fc7aa4152a.png&quot;&gt;В этой статье продолжим разбор внутреннего устройства программ на .NET, поговорим о программном стеке вызовов и работе с ним, а также о возникновении исключений и их обработке.</summary><content type="html">
  &lt;p id=&quot;BY4L&quot;&gt;В этой статье продолжим разбор внутреннего устройства программ на .NET, поговорим о программном стеке вызовов и работе с ним, а также о возникновении исключений и их обработке.&lt;/p&gt;
  &lt;figure id=&quot;MJpj&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/9f/a0/9fa0d71b-6111-4339-a622-d6fc7aa4152a.png&quot; width=&quot;1200&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;BZ4o&quot;&gt;Стек&lt;/h3&gt;
  &lt;p id=&quot;F6lj&quot;&gt;Стек — структура данных, работающая по принципу LIFO(last in first out - последний вошел - первым вышел). Добавление элементов в стек происходит с одного конца, вершины стека. Удаление из стека происходит также, начиная с вершины стека, то есть с последнего добавленного элемента по порядку.&lt;/p&gt;
  &lt;figure id=&quot;FrzO&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://evileg.com/media/users/Lila25mila/photos/photo_ILE5mbe.jpg&quot; width=&quot;461&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;BKDo&quot;&gt;Принцип работы стека можно сравнить с обоймой - доступ мы можем получить только к верхнему элементу в стеке, для доступа к добавленному перед ним, необходимо удалить данный верхний элемент.&lt;/p&gt;
  &lt;p id=&quot;Qq8V&quot;&gt;В C# стек представлен в виде коллекции &lt;code&gt;Stack&lt;/code&gt; из пространства имен &lt;code&gt;System.Collections&lt;/code&gt; и универсальной версией &lt;code&gt;Stack&amp;lt;T&amp;gt;&lt;/code&gt; из &lt;code&gt;System.Collections.Generic&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;QnSU&quot;&gt;Таким образом, для стека обязательно должны реализовываться три основных метода:&lt;/p&gt;
  &lt;ul id=&quot;FHTk&quot;&gt;
    &lt;li id=&quot;41cT&quot;&gt;&lt;code&gt;Push&lt;/code&gt; - добавление нового элемента в вершину;&lt;/li&gt;
    &lt;li id=&quot;hmBU&quot;&gt;&lt;code&gt;Pop&lt;/code&gt; - получение верхнего элемента при его удалении;&lt;/li&gt;
    &lt;li id=&quot;Xykn&quot;&gt;&lt;code&gt;Peek&lt;/code&gt; - чтение верхнего элемента без удаления.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;ZNZp&quot; data-lang=&quot;clike&quot;&gt;var stack = new Stack&amp;lt;int&amp;gt;();
stack.Push(1);
stack.Push(2);
stack.Push(3);

Console.WriteLine(stack.Peek()); //3

Console.WriteLine(stack.Pop());  //3
Console.WriteLine(stack.Peek()); //2

stack.Pop();
Console.WriteLine(stack.Peek()); //1&lt;/pre&gt;
  &lt;h3 id=&quot;GMHG&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;nxfE&quot;&gt;Программный стек&lt;/h3&gt;
  &lt;p id=&quot;6hh4&quot;&gt;У нас уже заходила речь в одной из предыдущих &lt;a href=&quot;https://teletype.in/@notserious_seesharp/classes_basic#bKfN&quot; target=&quot;_blank&quot;&gt;статей&lt;/a&gt; о том, что на стеке размещаются значение переменной типа значения, если оно является локальной переменной/аргументом/возвращаемым значением метода, а также ссылки на объекты в куче (если объект не является частью ссылочного типа).&lt;/p&gt;
  &lt;p id=&quot;twLs&quot;&gt;Так вот для этих целей используется программный стек выполнения - заранее выделяемый под каждую программу участок памяти размером обычно 1 мб, который организован по принципу структуры данных стек.&lt;/p&gt;
  &lt;p id=&quot;LfT3&quot;&gt;Таким образом, при каждом вызове метода .NET инициализирует стек-фрейм (кадр стека, можно условно ассоциировать с контейнером), где и хранится вся необходимая информация для выполнения метода: параметры, локальные переменные, служебная информация, которая используется вызванной функцией, чтобы в нужный момент возвратить управление вызвавшей функции. Каждый такой новый фрейм (контейнер вызваемого метода) помещается на вершину стека и удаляется после завершения выполнения метода. У программного стека также есть особенность, что он растет в адресном пространстве памяти не снизу вверх, а сверху вниз.&lt;/p&gt;
  &lt;figure id=&quot;WRFY&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/20/1c/201c715c-ec4f-4dc0-8716-7d9161473a2a.png&quot; width=&quot;712&quot; /&gt;
    &lt;figcaption&gt;Пимер программы и упрощенное представление стека вызовов&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Q9hn&quot;&gt;Мы не будем сейчас вдаваться в подробности устройства стека вызовов. На текущем этапе лишь важно понимать, что стек вызовов служит для хранения порядка выполнения кода, то есть для хранения адресов возврата в порядке вызова методов, которые впоследствии используются для возврата из метода, а также локальные переменные. При вызове метода фрейм метода добавляется на вершину стека, а при его завершении удаляется. Таким образом, программа завершается после после того, как цепочка вызова всех методов становится пустой, то есть программа будет работать до тех пор, пока стек вызовов не станет пустым.&lt;/p&gt;
  &lt;p id=&quot;TGKT&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;RhIQ&quot;&gt;StackTrace и StackFrame&lt;/h3&gt;
  &lt;p id=&quot;NBvE&quot;&gt;При работе программы могут возникать непредвиденные ошибки, исключения (о них мы поговорим чуть дальше), например деление на ноль. И для того, чтобы программист исправил возможную свою ошибку в коде или обработал исключение, ему неплохо было бы вообще понять место возникновения данной &amp;quot;ошибки&amp;quot;. В этих целях и полезен стек вызовов, откуда можно достать всю необходимую служебную информацию.&lt;/p&gt;
  &lt;p id=&quot;8f92&quot;&gt;В C# для получения информации о стеке существует класс &lt;code&gt;StackTrace&lt;/code&gt; из пространства имен &lt;code&gt;System.Diagnostics&lt;/code&gt;, а за кадр (фрейм) стека отвечает класс &lt;code&gt;StackFrame&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;8vnc&quot;&gt;Трассировка стека - цепочка вызова методов, которые находятся в стеке на данный момент. То есть в любой момент выполнения кода мы можем рассмотреть состояние стека вызовов, что и будет являться трассировкой. Каждый же кадр стека знает кто его вызывает, но не знает о существовании кадров, которые будет порождать он, так как на момент обращения к текущему кадру, кадров выше не существует (они либо уже выполнены, либо еще не созданы).&lt;/p&gt;
  &lt;pre id=&quot;hSoZ&quot; data-lang=&quot;clike&quot;&gt;using System.Diagnostics;

class Program
{
    public static void Method1()
    {
        Method2();
    }

    public static void Method2()
    {
        Method3();
    }

    public static void Method3()
    {
        StackTrace currentTrace = new StackTrace(true);
        Console.WriteLine($&amp;quot;Количество фреймов: {currentTrace.FrameCount}&amp;quot;);

        for (int i = 0; i &amp;lt; currentTrace.FrameCount; i++)
        {
            StackFrame frame = currentTrace.GetFrame(i);

            Console.WriteLine();
            Console.WriteLine($&amp;quot;Метод {frame.GetMethod().Name} из файла {frame.GetFileName()} &amp;quot; +
                $&amp;quot;расположен на строчке {frame.GetFileLineNumber()}&amp;quot;);
        }
    }

    public static void Main(string[] args)
    {
        Method1();
    }
}&lt;/pre&gt;
  &lt;p id=&quot;HUhS&quot;&gt;В данном примере в методе &lt;code&gt;Method3()&lt;/code&gt; мы создаем объект трассировки &lt;code&gt;StackTrace&lt;/code&gt;, а затем получаем поочередно все фреймы стека, начиная с верхнего, нулевого, и заканчивая нижним фреймом метода &lt;code&gt;Main&lt;/code&gt;. Таким нехитрым образом получается достать много полезной информации о нашем коде, в данном примере мы выводим имена цепочки вызывающих методов и их расположение, с помощью методов объекта фрейма &lt;code&gt;GetFileName()&lt;/code&gt; и &lt;code&gt;GetFileLineNumber()&lt;/code&gt;. &lt;br /&gt;&lt;em&gt;Нужно отметить, что для того, чтобы объект класса &lt;code&gt;StackTrace&lt;/code&gt; собрал дополнительную инфу о расположении методов в коде необходимо использовать конструктор с булевым параметром &lt;code&gt;fNeedFileInfo&lt;/code&gt; и передать в него &lt;code&gt;true&lt;/code&gt;, иначе он не соберет данную информацию и мы получим на выходе методов &lt;code&gt;GetFileName()&lt;/code&gt; и &lt;code&gt;GetFileLineNumber()&lt;/code&gt; значения по умолчанию (&lt;code&gt;null&lt;/code&gt; и &lt;code&gt;0&lt;/code&gt; соответственно).&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;SGjz&quot;&gt;Но не будем сейчас подробно останавливаться на стек трейсе об этом мы еще успеем поговорить в теме рефлексии. Это все был важный материал для понимания работы исключений, о которых далее и пойдет речь)&lt;/p&gt;
  &lt;p id=&quot;JbaU&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;vd0f&quot;&gt;Ошибки и исключения&lt;/h3&gt;
  &lt;p id=&quot;yjnt&quot;&gt;При разработке программ и их поддержке разработчики так или иначе сталкиваются с различными проблемами и ошибками кода. В одних случаях проблемы возникают из-за &amp;quot;плохого&amp;quot; кода, из-за чего работа приложения будет некорректной, а в других — из-за некорректного пользовательского ввода данных, которые не были учтены в коде приложения. Так или иначе приложение начинает приложение начинает при возникновении данных проблем начинает работать не так, как задумано, и программисту необходимо данные ошибки находить и исправлять/обрабатывать. &lt;/p&gt;
  &lt;p id=&quot;eJ9Z&quot;&gt;При разработке программного обеспечения мы сталкиваемся с тремя основными типами ошибок:&lt;/p&gt;
  &lt;ul id=&quot;mcnc&quot;&gt;
    &lt;li id=&quot;4n9h&quot;&gt;Программные ошибки, баги (bug - жук:) — ошибки в коде, допущенные программистом, приводящие к некорректной работе программы. Например, при обращении к элементам массива в цикле происходит выход за его пределы. Баги могут быть как простые вроде приведенного примера, так и с нарушением более глобальной сложной логики программы, и должны исправляться в процессе отладки кода программы.&lt;/li&gt;
    &lt;li id=&quot;JSjq&quot;&gt;Пользовательские ошибки — ошибки, которые допускает конечный пользователь во время работы с программой. Например, некорректный пользовательский ввод в текстовое поле, в котором ожидался ввод почты пользователя, а получили набор цифр, что может привести к возможным ошибкам в дальнейшей работе с программой. Такие проблемы обычно решаются путем добавления валидации входных данных.&lt;/li&gt;
    &lt;li id=&quot;KoFI&quot;&gt;Исключения — ситуации, которые не предусмотрены стандартным поведением программы. Например, когда коде программы мы открываем несуществующий файл для чтения из него или пытаемся подключиться к web-ресурсу, который по какой-то причине недоступен (его забанило правительственное ведомство нашего государства, а у нас не включен vpn:). Такие случаи обычно не зависят от программиста, и при их возникновении, чтобы программа не &amp;quot;падала&amp;quot; от непонимания, что делать дальше, эти исключительные ситуации необходимо обрабатывать.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;BIyS&quot;&gt;В рамках .NET CLR будет генерировать соответствующие исключения даже для программных и пользовательских ошибок, которые не учел программист.&lt;/p&gt;
  &lt;p id=&quot;SCDi&quot;&gt;В языках, где механизмы для работы с исключениями не стандартизированы, программистам приходится придумывать свою логику для обработки ошибок (например, тот же C). Причем данная логика у всех команд разработчиков будет так или иначе своей, что несет за собой дополнительные сложности при понимании кода и вкатывании в проект. Одним из таких подходов обработки ошибок на языке C, где нет для этого стандартных средств, являлось определение числовых констант кодов ошибок, с помощью макросов (через директиву #define), и булевых флагов. В случае возникновения ошибки - функция возвращала определенный числовой код, и по полученному коду программистом выстраивалась логика действий по обработке. В случае с булевым флагом - флагу в глобальной области видимости присваивается соответствующие значение, если произошла ошибка, и на основании этого, после завершения функции, выстраивается логика по обработке.&lt;/p&gt;
  &lt;pre id=&quot;7gnv&quot; data-lang=&quot;c&quot;&gt;/* Пример обработки ошибок в С, с помощью кодов */
#define CONN_FAIL 400  //объявление кода ошибки

int ConnDatabase()
{
    //Здесь происходит какая-то логика по подключению к базе,
    //но по какой-то причине могла возникнуть ошибка подключения,
    //в результате чего возвращается следующее значение
    return CONN_FAIL;
}

int main()
{
    int res = ConnDatabase();
    if (res == CONN_FAIL)
    {
        printf(&amp;quot;Fail connection to db&amp;quot;);
    }
    
    return 0;
}&lt;/pre&gt;
  &lt;pre id=&quot;lCHr&quot; data-lang=&quot;cpp&quot;&gt;/* Пример обработки ошибок в С, с помощью булевого флага */
bool error;

double Div(double a, double b)
{
    if (b == 0)
    {
        error = true;
        return NULL;
    }
    
    return a / b;
}

int main()
{
    error = false;
    double res = Div(5, 0);
    if (error == true)
    {
        printf(&amp;quot;Zero division error&amp;quot;);
    }
    else
    {
        printf(&amp;quot;Result: %f&amp;quot;, res);
    }

    return 0;
}&lt;/pre&gt;
  &lt;p id=&quot;L0Qq&quot;&gt;При таком подходе в рамках одного проекта может быть определена куча таких кодов, с которыми придется работать присоединяющимся к проекту новым программистам. Но проблема кроется не только в том, что кодов может быть много, но и в том, что нет никакой стандартизации их объявления, то есть числовые коды одного проекта могут обозначать совершенно другие ситуации в другом, а при подключении какой-то библиотеки к проекту они и вовсе могут совпасть, но не по смыслу:)&lt;/p&gt;
  &lt;figure id=&quot;ktsA&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/b7/4e/b74ed3ad-de1f-4e9d-85fd-b8feb97364e5.jpeg&quot; width=&quot;600&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;fk5d&quot;&gt;Для недопущения этого безобразия, в C#, как в любом нормальном современном япе, есть стандартные средства для генерации и обработки исключений, что не может не радовать, а генерируемые исключения являются объектами, которые содержат в себе различную полезную информацию о том, что произошло и где, в отличие от непонятных числовых кодов (документация по которым в сделку проекта естественно не входила:)&lt;/p&gt;
  &lt;h3 id=&quot;hbvH&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;2Ol2&quot;&gt;Исключения в C#&lt;/h3&gt;
  &lt;p id=&quot;Qx8W&quot;&gt;Как уже было сказано ранее, исключение - ситуация, которая не предусмотрена стандартным поведением программы. В C# все исключения происходят от базового класса &lt;code&gt;System.Exception&lt;/code&gt;, а все остальные классы исключений – это производные классы, которые позволяют конкретизировать тип возникшего исключения. &lt;/p&gt;
  &lt;p id=&quot;IQ41&quot;&gt;Независимо от того, какое исключение было сгенерировано, данное исключение будет иметь функциональность, которая наследуется от базового класса &lt;code&gt;Exception&lt;/code&gt;. Таким образом все исключения имеют ряд полезных свойств, в которых отражается информация о причинах возникновения исключения и места:&lt;/p&gt;
  &lt;ul id=&quot;QBQP&quot;&gt;
    &lt;li id=&quot;YYQY&quot;&gt;&lt;code&gt;Message&lt;/code&gt; — свойство с текстовым описанием ошибки, использующееся только для чтения и устанавливающееся в конструкторе;&lt;/li&gt;
    &lt;li id=&quot;BXK2&quot;&gt;&lt;code&gt;HelpLink&lt;/code&gt; — свойство для указания или получения URL-ссылки для доступа к веб-странице с подробным описанием ошибки;&lt;/li&gt;
    &lt;li id=&quot;Nvi2&quot;&gt;&lt;code&gt;Source&lt;/code&gt; — свойства служащее для указания названия сборки или объекта, в котором возникло исключение;&lt;/li&gt;
    &lt;li id=&quot;fx5d&quot;&gt;&lt;code&gt;TasrgetSite&lt;/code&gt; — свойство, доступное только для чтения, в котором содержится различная информация о методе, в котором возникло исключение;&lt;/li&gt;
    &lt;li id=&quot;oBQD&quot;&gt;&lt;code&gt;StackTrace&lt;/code&gt; — свойство, доступное только для чтения, которое содержит цепочку вызовов методов до возникновения исключения;&lt;/li&gt;
    &lt;li id=&quot;V0n8&quot;&gt;&lt;code&gt;InnerException&lt;/code&gt; — свойство, доступное только для чтения, которое может использоваться для получения информации о предыдущих исключениях, которые послужили причиной возникновения текущего исключения. Запись предыдущих исключений осуществляется путем их передачи конструктору самого последнего сгенерированного исключения.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;yUI4&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;3tvf&quot;&gt;Генерация исключений&lt;/h3&gt;
  &lt;p id=&quot;CO6G&quot;&gt;При возникновении определенных системных ошибок при работе программы CLR самостоятельно генерирует соответствующие исключения. А все неперехваченные исключения приводят к аварийному завершению программы, и скорее всего вы уже сталкивались с данными ситуациями. &lt;/p&gt;
  &lt;p id=&quot;ffRB&quot;&gt;Вот простейший пример деления на 0, в результате чего CLR выбросит исключение:&lt;/p&gt;
  &lt;pre id=&quot;z2i2&quot; data-lang=&quot;clike&quot;&gt;class Program
{
    public static int Div(int a, int b)
    {
        return a / b;
    }

    public static void Main()
    {
        Console.WriteLine(Div(5, 0));
        Console.WriteLine(Div(5, 1));
    }
}&lt;/pre&gt;
  &lt;figure id=&quot;1tOu&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/83/7b/837b5e57-59c4-494b-982a-ac01a297abba.png&quot; width=&quot;860&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;fCzd&quot;&gt;Код выше будет падать из-за вызванного неперехваченного исключения в первом вызове метода &lt;code&gt;Div()&lt;/code&gt; при делении в нем на 0. В результате в консоли мы получим сообщение с информацией об исключении и стектрейсе, из которого мы можем понять, что было вызвано исключение &lt;code&gt;DevideByZeroException&lt;/code&gt; в строке &lt;code&gt;5&lt;/code&gt; метода &lt;code&gt;Div()&lt;/code&gt;, при его вызове из строки &lt;code&gt;10&lt;/code&gt; в файле &lt;code&gt;Program.cs&lt;/code&gt;. &lt;em&gt;Умение читать такие сообщения со стектрейсом является важным навыком для поиска ошибок в коде.&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;4IE7&quot;&gt;Для того, чтобы сгенерировать исключение вручную используется ключевое слово &lt;code&gt;throw&lt;/code&gt; с объектом исключения.&lt;/p&gt;
  &lt;pre id=&quot;XYeG&quot; data-lang=&quot;clike&quot;&gt;//создаем объект исключения
var ex = new Exception(&amp;quot;Wow, its exception message&amp;quot;);

//генерируем(выбрасываем) исключение
throw ex;&lt;/pre&gt;
  &lt;p id=&quot;gGXd&quot;&gt;Но нам не обязательно нужно сначала создавать объект исключения, а лишь потом его выбрасывать, обычно это делается в то же время:&lt;/p&gt;
  &lt;pre id=&quot;cArh&quot; data-lang=&quot;clike&quot;&gt;throw new Exception(&amp;quot;Wow, its exception message&amp;quot;);&lt;/pre&gt;
  &lt;p id=&quot;fu3Z&quot;&gt;Мы можем в своих методах генерировать любые исключения классов, унаследованных от &lt;code&gt;Exception&lt;/code&gt;, это при дальнейшем анализе дает больше конкретики о том, что произошло. В предыдущем примере с делением CLR сгенерировал исключение &lt;code&gt;DivideByZeroException&lt;/code&gt;, что сразу говорит о произошедшим, в то время как я показал генерацию базового &lt;code&gt;Exception&lt;/code&gt; с непонятным сообщением (не надо так делать:).&lt;/p&gt;
  &lt;p id=&quot;nD8G&quot;&gt;Давайте возьмем пример с делением и бросим подходящее исключение самостоятельно с каким-то логичным сообщением о произошедшем, опередив CLR:&lt;/p&gt;
  &lt;pre id=&quot;r7yh&quot; data-lang=&quot;clike&quot;&gt;class Program
{
    public static int Div(int a, int b)
    {
        if (b == 0)
            throw new DivideByZeroException(&amp;quot;Division by 0 is not allowed!!!!&amp;quot;);

        return a / b;
    }

    public static void Main()
    {
        Console.WriteLine(Div(5, 0));
    }
}&lt;/pre&gt;
  &lt;figure id=&quot;ZJsk&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/08/64/0864d718-2e38-49f0-ba0b-0f226fd15069.png&quot; width=&quot;937&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;4GGb&quot;&gt;Предлагаю еще рассмотреть еще одну ситуация с генерацией и переходим к обработки исключений. &lt;/p&gt;
  &lt;p id=&quot;vUrh&quot;&gt;Допустим, мы в методе используем в качестве параметра ссылочный тип и передаем при вызове в качестве аргумента ссылку на &lt;code&gt;null&lt;/code&gt;. В таком случае подходящим типом для исключения будет &lt;code&gt;ArgumentNullException&lt;/code&gt;, который в конструкторе принимает в качестве первого параметра имя параметра, с которым произошла проблема:&lt;/p&gt;
  &lt;pre id=&quot;4GGb&quot; data-lang=&quot;clike&quot;&gt;class Program
{
    public static void Hello(string name)
    {
        if (name == null)
            throw new ArgumentNullException(nameof(name), &amp;quot;Name must not be null or empty&amp;quot;);

        Console.WriteLine($&amp;quot;Hello {name}&amp;quot;);
    }

    public static void Main()
    {
        Hello(null);
    }
}&lt;/pre&gt;
  &lt;figure id=&quot;VlBc&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/19/ab/19abfa23-87fd-4d32-a189-914f72ecf71d.png&quot; width=&quot;922&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;PHh2&quot;&gt;Ситуация с &lt;code&gt;null&lt;/code&gt; действительно типовая и часто встречается в начале общедоступных методов для проверки на возможное нежелательное значение параметров. Из-за того, что таких параметров могло быть много и производилась куча проверок, загрязняя код, в .NET 6 классу &lt;code&gt;ArgumentNullException&lt;/code&gt; был добавлен статический метод &lt;code&gt;ThrowIfNull&lt;/code&gt;, который генерирует соответствующее исключение, если параметр равен &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;tJPv&quot; data-lang=&quot;clike&quot;&gt;class Program
{
    public static void Hello(string name, string id)
    {
        ArgumentNullException.ThrowIfNull(name, nameof(name));
        ArgumentNullException.ThrowIfNull(id, nameof(id));

        Console.WriteLine($&amp;quot;Hello {name} with {id}&amp;quot;);
    }

    public static void Main()
    {
        Hello(&amp;quot;Artem&amp;quot;, null);
    }
}&lt;/pre&gt;
  &lt;figure id=&quot;USC2&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/97/63/97639684-e20f-4365-b1ad-74a90749a769.png&quot; width=&quot;878&quot; /&gt;
    &lt;figcaption&gt;Согласитесь, это выглядит гораздо компактнее)&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;VDAY&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Vs6K&quot;&gt;Обработка исключений&lt;/h3&gt;
  &lt;p id=&quot;zFVQ&quot;&gt;Как уже было сказано, неперехваченные исключения приводят к аварийному завершению программы, так что вызывать код генерирующий исключение и никак его не обрабатывать - не круто. Для перехвата и обработки исключений используется конструкция &lt;code&gt;try catch finally&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;sNPi&quot; data-lang=&quot;clike&quot;&gt;try
{
    // Блок с потенциально проблемным кодом, 
    // который может сгенерировать исключение
}
catch(Exception ex)
{
    // Блок перехвата исключения, 
    // если оно было сгенерировано
}
finally
{
    // Финальный блок для закрытия каких-либо ресурсов,
    // который выполняется всегда в независимости,
    // было вызвано исключение или нет
}&lt;/pre&gt;
  &lt;p id=&quot;8Ppd&quot;&gt;Алгоритм работы данной конструкции сводится к следующему:&lt;/p&gt;
  &lt;ul id=&quot;4xPM&quot;&gt;
    &lt;li id=&quot;qQLw&quot;&gt;В блок &lt;code&gt;try&lt;/code&gt; помещается код, который в ходе выполнения может привести к возникновению исключений. &lt;/li&gt;
    &lt;li id=&quot;IIEq&quot;&gt;Если в блоке &lt;code&gt;try&lt;/code&gt; возникает исключение, то CLR останавливает выполнение инструкций в &lt;code&gt;try&lt;/code&gt; и начинает искать подходящий блок &lt;code&gt;catch&lt;/code&gt;, который предназначен для обработки данного исключения. Для блока catch мы указываем тип перехватываемого исключения, а внутри блока уже прописываем необходимые инструкции, которые необходимо выполнить для обработки исключения, например, зафиксировать каким-либо образом сообщение об ошибке: вывести в лог, отправить сообщение о проблеме на почту или в телегу.&lt;/li&gt;
    &lt;li id=&quot;twsC&quot;&gt;Блок &lt;code&gt;finally&lt;/code&gt; выполняется в конце всегда независимо, было вызвано исключение или нет, за исключением пары случаев, но о них позже.&lt;/li&gt;
    &lt;li id=&quot;wM8k&quot;&gt;Если при вызванном исключении оно было успешно &amp;quot;отловлено&amp;quot; с помощью &lt;code&gt;catch&lt;/code&gt;, после блока &lt;code&gt;finally&lt;/code&gt; выполнение переходит на следующую за данным блоком инструкцию.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;R7l4&quot;&gt;Например, вот так мы можем отловить исключение при нашем делении на 0, и вывести сообщение об ошибке.&lt;/p&gt;
  &lt;pre id=&quot;nfzC&quot; data-lang=&quot;clike&quot;&gt;class Program
{
    public static int Div(int a, int b)
    {
        if (b == 0)
            throw new DivideByZeroException(&amp;quot;Division by 0 is not allowed!!!!&amp;quot;);

        return a / b;
    }

    public static void Main()
    {
        try
        {
            int res = Div(5, 0);
            Console.WriteLine($&amp;quot;Result: {res}&amp;quot;);
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine($&amp;quot;Error: {ex.Message}&amp;quot;);
        }
        finally
        {
            Console.WriteLine(&amp;quot;End divide&amp;quot;);
        }

        Console.WriteLine(&amp;quot;End program&amp;quot;);   
    }
}&lt;/pre&gt;
  &lt;figure id=&quot;5LvC&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e0/84/e084eb60-ae9c-48d2-8d72-6a0055420977.png&quot; width=&quot;714&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;gaRh&quot;&gt;Следует выделить несколько правил конструкции &lt;code&gt;try catch finally&lt;/code&gt;:&lt;/p&gt;
  &lt;ol id=&quot;b09z&quot;&gt;
    &lt;li id=&quot;IP6U&quot;&gt;При использовании &lt;code&gt;try&lt;/code&gt; всегда должен использоваться как минимум один &lt;code&gt;catch&lt;/code&gt; либо &lt;code&gt;finally&lt;/code&gt;;&lt;/li&gt;
    &lt;li id=&quot;Z1pe&quot;&gt;Блоков &lt;code&gt;catch&lt;/code&gt; может быть много, либо ни одного, но они не могут существовать без блока &lt;code&gt;try&lt;/code&gt;.&lt;/li&gt;
    &lt;li id=&quot;HyGd&quot;&gt;Блок &lt;code&gt;finally&lt;/code&gt; не является обязательным при использовании &lt;code&gt;try catch&lt;/code&gt;, но, если он используется, то он может быть только один.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;12cB&quot;&gt;&lt;em&gt;То есть при наличии блока &lt;code&gt;catch&lt;/code&gt; мы можем опустить блок &lt;code&gt;finally&lt;/code&gt;, и, наоборот, при наличии блока &lt;code&gt;finally&lt;/code&gt; мы можем опустить блок &lt;code&gt;catch&lt;/code&gt; и не обрабатывать исключение. Но если мы не обработаем исключение, то блок &lt;code&gt;finally&lt;/code&gt; отработает, а дальше приложение аварийно завершит работу, в случае возникновения исключения, как уже говорилось ранее.&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;Nwkb&quot;&gt;Так как в предыдущем примере нам не нужна какая-то дополнительная логика для закрытия ресурсов, то и блок &lt;code&gt;finally&lt;/code&gt; нет никакого смысла использовать в данном случае и его можно опустить:&lt;/p&gt;
  &lt;pre id=&quot;XPlk&quot; data-lang=&quot;clike&quot;&gt;try
{
    int res = Div(5, 0);
    Console.WriteLine($&amp;quot;Result: {res}&amp;quot;);
}
catch (DivideByZeroException ex)
{
    Console.WriteLine($&amp;quot;Error: {ex.Message}&amp;quot;);
}

Console.WriteLine(&amp;quot;End program&amp;quot;);&lt;/pre&gt;
  &lt;p id=&quot;4PuO&quot;&gt;В реальном коде код внутри блока &lt;code&gt;try&lt;/code&gt; может быть гораздо сложнее и генерировать более одного исключения. Для обработки каждого случая можно использовать множество блоков &lt;code&gt;catch&lt;/code&gt; для каждого конкретного типа исключения. &lt;/p&gt;
  &lt;p id=&quot;N5v2&quot;&gt;Давайте добавим в наш пример возможность считывания чисел для деления с клавиатуры:&lt;/p&gt;
  &lt;pre id=&quot;Wocn&quot; data-lang=&quot;clike&quot;&gt;try
{
    Console.Write(&amp;quot;Enter numerator: &amp;quot;);
    int a = int.Parse(Console.ReadLine());

    Console.Write(&amp;quot;Enter denumerator: &amp;quot;);
    int b = int.Parse(Console.ReadLine());

    int res = Div(a, b);
    Console.WriteLine($&amp;quot;Result: {res}&amp;quot;);
}
catch (DivideByZeroException ex)
{
    Console.WriteLine($&amp;quot;Error: {ex.Message}&amp;quot;);
}&lt;/pre&gt;
  &lt;p id=&quot;Bhst&quot;&gt;Теперь при вводе в консоле не чисел для числителя и знаменателя метод &lt;code&gt;Parse()&lt;/code&gt; будет генерировать исключение  &lt;code&gt;FormatException&lt;/code&gt;, которое не будет перехвачено с помощью блока &lt;code&gt;catch&lt;/code&gt;, т.к. он перехватывает в данном случае только &lt;code&gt;DivideByZeroException&lt;/code&gt;, и программа аварийно завершится в таком случае. Чтобы этого избежать, необходимо добавить блок &lt;code&gt;catch&lt;/code&gt; для обработки данных исключений:&lt;/p&gt;
  &lt;pre id=&quot;CJkB&quot; data-lang=&quot;clike&quot;&gt;try
{
    Console.Write(&amp;quot;Enter numerator: &amp;quot;);
    int a = int.Parse(Console.ReadLine());

    Console.Write(&amp;quot;Enter denumerator: &amp;quot;);
    int b = int.Parse(Console.ReadLine());

    int res = Div(a, b);
    Console.WriteLine($&amp;quot;Result: {res}&amp;quot;);
}
catch (DivideByZeroException ex)
{
    Console.WriteLine($&amp;quot;Error: {ex.Message}&amp;quot;);
}
catch (FormatException ex)
{
    Console.WriteLine($&amp;quot;Error: {ex.Message}&amp;quot;);
    Console.WriteLine(&amp;quot;You entered not a number!!!&amp;quot;);
}
catch (Exception ex)
{
    Console.WriteLine($&amp;quot;Unreported error: {ex.Message}&amp;quot;);
}&lt;/pre&gt;
  &lt;p id=&quot;YxQr&quot;&gt;Но при написании нескольких блоков &lt;code&gt;catch&lt;/code&gt; нужно учитывать, что сгенерированное исключение будет обрабатываться первым подходящим боком &lt;code&gt;catch&lt;/code&gt;, то есть блоки &lt;code&gt;catch&lt;/code&gt; должны быть расположены в своей последовательности так, чтобы первый &lt;code&gt;catch&lt;/code&gt; перехватывал наиболее специфическое исключение (то есть производный тип, расположенный ниже всех в цепочке наследования типов исключений), а последний &lt;code&gt;catch&lt;/code&gt; — самое общее исключение (то есть базовый класс &lt;code&gt;Exception&lt;/code&gt;). Если же расположить &lt;code&gt;catch&lt;/code&gt; с более базовым классом перед остальными, например &lt;code&gt;Exception&lt;/code&gt;, то это будет ошибка компиляции и у нас ничего не получится, так как блоки &lt;code&gt;catch&lt;/code&gt; за ним будут являться недостижимыми.&lt;/p&gt;
  &lt;p id=&quot;vH0D&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;YplS&quot;&gt;Исключения в стеке вызовов&lt;/h3&gt;
  &lt;p id=&quot;wXSH&quot;&gt;При возникновении исключения в ходе выполнения программы CLR отвечает за поиск соответствующего обработчика &lt;code&gt;catch&lt;/code&gt;. Для этого среда CLR в обратном порядке начинает проходить через стек вызовов, начиная с фрейма текущего метода, в котором возникло исключение, т.к. он находится на вершине стека. Если CLR находит подходящий обработчик, то передает ему объект исключения, а если нет, то переходит к предыдущему фрейму вверх по стеку. Если же, пройдя через весь стек вызовов, CLR не найдет подходящий обработчик, она выполнит обработку исключения по умолчанию и аварийно завершит работу программы.&lt;/p&gt;
  &lt;p id=&quot;xbJh&quot;&gt;Разберем на примере из 4-х методов, которые вызываются по цепочке, и в последнем возникает исключение:&lt;/p&gt;
  &lt;pre id=&quot;WFJu&quot; data-lang=&quot;clike&quot;&gt;class Program
{
    public static void Method1()
    {
        try
        {
            Method2();
        }
        catch(NotImplementedException ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine(ex.StackTrace);
            Console.WriteLine();
        }

        Console.WriteLine(&amp;quot;Продолжаем выполнение&amp;quot;);
    }

    public static void Method2()
    {
        Method3();
        Console.WriteLine(&amp;quot;Это сообщение не напечатается&amp;quot;);
    }

    public static void Method3()
    {
        try
        {
            throw new NotImplementedException(&amp;quot;Исключение из метода 3&amp;quot;);
        }
        catch(IOException ex)
        {
            Console.WriteLine(&amp;quot;Тут не поймайется исключение&amp;quot;);
        }
    }

    public static void Main()
    {
        Method1();
    }
}&lt;/pre&gt;
  &lt;p id=&quot;kzCF&quot;&gt;Method3() находится на вершине стека вызовов, когда в нем возникает исключение &lt;code&gt;NotImplementedException&lt;/code&gt;. CLR не найдет подходящего &lt;code&gt;catch&lt;/code&gt; в &lt;code&gt;Method3()&lt;/code&gt; и перейдет к следующему блоку &lt;code&gt;try&lt;/code&gt; в стеке вызовов, предварительно удалив поочередно с вершины стека фреймы &lt;code&gt;Method3&lt;/code&gt; и &lt;code&gt;Method2&lt;/code&gt;, добавляя информацию о них в стек трейс исключения. В Method1() найдется подходящий обработчик, поэтому выполнение продолжится с  него.&lt;/p&gt;
  &lt;figure id=&quot;TiHC&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/45/0b/450b88fb-ccc4-42c4-8d60-d734242c6d37.png&quot; width=&quot;887&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;6ulW&quot;&gt;Таким образом, исключения являются довольно затратным механизмом и к ним лучше прибегать в крайнем случае, а если все же применяем, то не нужно тянуть исключение через весь стек, лучше его обрабатывать в том же методе, который и вызывает проблемный код.&lt;/p&gt;
  &lt;p id=&quot;Pskk&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;UXI5&quot;&gt;Повторная генерация исключений&lt;/h3&gt;
  &lt;p id=&quot;5C0P&quot;&gt;Иногда все же бывает необходимо обработать исключение, а затем, повторно сгенерировав, &amp;quot;отправить&amp;quot; вверх по стеку. Но способов повторной генерации существует несколько, и выбор &amp;quot;неправильного&amp;quot; приводит к изменению информации об исключении в &lt;code&gt;StackTrace&lt;/code&gt;, что будет весьма затруднять поиск проблемы и путать.&lt;/p&gt;
  &lt;p id=&quot;YUL6&quot;&gt;Один из вариантов, использовать обычную генерацию исключения с помощью &lt;code&gt;throw ex&lt;/code&gt;, с перехваченным объектом исключения &lt;code&gt;ex&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;NATg&quot; data-lang=&quot;clike&quot;&gt;class Program
{
    public static void Method1()
    {
        try
        {
            Method2();
        }
        catch(NotImplementedException ex)
        {
            throw ex;
        }
    }

    public static void Method2()
    {
        Method3();
    }

    public static void Method3()
    {
        throw new NotImplementedException(&amp;quot;Исключение из метода 3&amp;quot;);
    }

    public static void Main()
    {
        try
        {
            Method1();
        }
        catch(Exception ex)
        {
            Console.WriteLine(ex.TargetSite);
            Console.WriteLine(ex.StackTrace);
        }   
    }
}&lt;/pre&gt;
  &lt;figure id=&quot;mKzE&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a3/4d/a34d3c43-7bfe-4d06-9248-303bb5dec37e.png&quot; width=&quot;913&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;12E5&quot;&gt;Такой подход будет заново генерировать исключение, то есть для него вся служебная информация о стек трейсе исключения будет новой, начиная от строки, где повторно было сгенерировано исключение. Хотя по факту мы могли ожидать информацию об исключении из &lt;code&gt;Method3()&lt;/code&gt;, что может ввести в заблуждение.&lt;/p&gt;
  &lt;p id=&quot;5nq5&quot;&gt;Для того чтобы бросить текущее перехваченное исключение дальше можно использовать ключевое слово &lt;code&gt;throw&lt;/code&gt; без объекта исключения, в таком случае информация будет та, которую мы и ожидали.&lt;/p&gt;
  &lt;pre id=&quot;69ZC&quot; data-lang=&quot;clike&quot;&gt;public static void Method1()
{
    try
    {
        Method2();
    }
    catch(NotImplementedException ex)
    {
        throw;
    }
}&lt;/pre&gt;
  &lt;figure id=&quot;KoxX&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/df/08/df08e7a3-b148-46f1-8a79-13de71e55c1d.png&quot; width=&quot;973&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;A8cH&quot;&gt;Но такой вариант не дает полной картины, если необходимо получить информацию, включая повторную генерацию. В таком случае мы можем создать новый объект исключения, обернув в него исходное:&lt;/p&gt;
  &lt;pre id=&quot;XlXW&quot; data-lang=&quot;clike&quot;&gt;public static void Method1()
{
    try
    {
        Method2();
    }
    catch(NotImplementedException ex)
    {
        throw new Exception(&amp;quot;Rethrown&amp;quot;, ex);
    }
}&lt;/pre&gt;
  &lt;figure id=&quot;bLJe&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e8/8c/e88c3deb-9d1c-421d-a0fe-d9bb9d9b2901.png&quot; width=&quot;878&quot; /&gt;
    &lt;figcaption&gt;В Main() изменил вывод на полный Console.WriteLine(ex)&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;8zjZ&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;6CKI&quot;&gt;Создание собственных классов исключений&lt;/h3&gt;
  &lt;p id=&quot;eQLg&quot;&gt;В .NET уже содержится множество системных исключений, генерируемых исполняющий средой, и которые можно использовать в своих целях. Но в некоторых ситуациях бывает нужно отделить возникающую проблему в приложении от системных исключений. В таком случае можно создать собственный класс исключений.&lt;/p&gt;
  &lt;p id=&quot;0HRy&quot;&gt;Для создания собственного класса исключений необходимо и достаточно унаследовать свой класс от &lt;code&gt;System.Exception&lt;/code&gt; или производного от него (о наследовании мы поговорим позже в лекциях об ООП:). Но есть четкая иерархия исключений, которой стоит придерживаться. &lt;code&gt;System.Exception&lt;/code&gt; является базовым общим классом для всех создаваемых исключений, от него напрямую наследуются классы &lt;code&gt;System.SystemException&lt;/code&gt; и &lt;code&gt;System.ApplicationException&lt;/code&gt;. &lt;code&gt;SystemException&lt;/code&gt; определяет исключения, генерируемые самой платформой .NET, а &lt;code&gt;ApplicationException&lt;/code&gt; определяет исключения, порождаемые приложением. &lt;/p&gt;
  &lt;figure id=&quot;yNsM&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;http://www.excoded.com/learn/csharp/images/ExceptionHierarchy.png&quot; width=&quot;625&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;B9AT&quot;&gt;Таким образом, Microsoft рекомендуют собственные классы исключений наследовать именно от &lt;code&gt;ApplecationException&lt;/code&gt;. Как уже было сказано, чтобы создать класс исключения достаточно его просто унаследовать от Exception или от унаследуемого от него.&lt;/p&gt;
  &lt;pre id=&quot;YFrZ&quot; data-lang=&quot;clike&quot;&gt;public class HouseOnFireException : ApplicationException
{
}&lt;/pre&gt;
  &lt;p id=&quot;PQqJ&quot;&gt;Но это не является хорошей практикой, т.к. мы не сможем передать даже никакое сообщение при генерации исключения. Для решения этой проблемы необходимо задать конструкторы, которые будут при создании объекта исключения обращаться к конструктору базового класса, с помощью &lt;code&gt;base&lt;/code&gt; (о нем мы также подробнее поговорим уже в курсе лекций по ООП:)&lt;/p&gt;
  &lt;pre id=&quot;D4lK&quot; data-lang=&quot;clike&quot;&gt;public class HouseOnFireException : ApplicationException
{
    public HouseOnFireException()
    {
    }

    public HouseOnFireException(string message) 
        : base(message)
    {
    }

    public HouseOnFireException(string message, Exception innerException) 
        : base(message, innerException)
    {
    }
}&lt;/pre&gt;
  &lt;p id=&quot;RjTX&quot;&gt;Все, теперь мы можем генерировать исключение нашего класса:)&lt;/p&gt;
  &lt;figure id=&quot;7zCk&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://assets.cdn.prod.twilio.com/images/A9ZDocG2PU43VT16UFxIBCXS5sGAf7lIYDH4UqDIqZLU-G.width-500.jpg&quot; width=&quot;500&quot; /&gt;
  &lt;/figure&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;SeCH&quot;&gt;Как-то так:) В следующей лекции поговорим еще раз про типизацию и разберем основы приведения типов, и наверно закончим с базой шарпа. А далее начнем курс лекций по ООП. Так что подписывайтесь на тг &lt;a href=&quot;https://t.me/serious_seesharp&quot; target=&quot;_blank&quot;&gt;канал&lt;/a&gt;, ставьте лайки и вступайте в наш &lt;a href=&quot;https://t.me/notserious_seesharp_chat&quot; target=&quot;_blank&quot;&gt;чат,&lt;/a&gt; там ничего нет.&lt;/p&gt;

</content></entry><entry><id>notserious_seesharp:method_parameters</id><link rel="alternate" type="text/html" href="https://teletype.in/@notserious_seesharp/method_parameters?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=notserious_seesharp"></link><title>Модификаторы параметров методов. Передача аргументов по значению и по ссылке</title><published>2022-11-14T17:27:44.225Z</published><updated>2024-01-05T19:06:19.955Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/04/d1/04d1ab7e-fbf2-47e2-a194-bef8c3080f36.png"></media:thumbnail><category term="osnovy-c" label="Основы C#"></category><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/e3/36/e3362ce8-1c8c-4eaa-9a09-ce98d290b067.png&quot;&gt;В предыдущих статьях, мы уже успели познакомиться с методами классов и для чего они нужны. В данной мы снова затронем тему методов, а конкретно поговорим об передачи данных при вызове метода.</summary><content type="html">
  &lt;p id=&quot;18er&quot;&gt;В предыдущих статьях, мы уже успели познакомиться с методами классов и для чего они нужны. В данной мы снова затронем тему методов, а конкретно поговорим об передачи данных при вызове метода.&lt;/p&gt;
  &lt;figure id=&quot;ZFvF&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e3/36/e3362ce8-1c8c-4eaa-9a09-ce98d290b067.png&quot; width=&quot;1200&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;YAhi&quot;&gt;Сигнатура метода, параметры и аргументы&lt;/h3&gt;
  &lt;p id=&quot;Kxq4&quot;&gt;Для начала давайте определимся с понятиями сигнатуры методов, аргументов и параметров, чтобы в дальнейшем не путаться.&lt;/p&gt;
  &lt;p id=&quot;ImD8&quot;&gt;Под сигнатурой метода понимают совокупность:&lt;/p&gt;
  &lt;ul id=&quot;toJL&quot;&gt;
    &lt;li id=&quot;DuJK&quot;&gt;имени метода;&lt;/li&gt;
    &lt;li id=&quot;dIFg&quot;&gt;количества параметров;&lt;/li&gt;
    &lt;li id=&quot;tJqS&quot;&gt;типов параметров;&lt;/li&gt;
    &lt;li id=&quot;14nS&quot;&gt;порядка типа параметров;&lt;/li&gt;
    &lt;li id=&quot;ZTmD&quot;&gt;модификаторов параметров.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;RugM&quot;&gt;При изменении имени метода мы получаем новый метод, а при измении любого другого пункта в сигнатуре мы получаем перегруженный метод)&lt;/p&gt;
  &lt;p id=&quot;CZfp&quot;&gt;Например, следующие два метода являются перегруженными по сигнатуре:&lt;/p&gt;
  &lt;pre id=&quot;htWy&quot; data-lang=&quot;clike&quot;&gt;class Robot
{
    public void SaySomething(string words, int a)
    {
        Console.WriteLine(words);
        Console.WriteLine(a + 3);
    }

    public void SaySomething(int a, string words)
    {
        Console.WriteLine(words);
        Console.WriteLine(a + 3);
    }
}&lt;/pre&gt;
  &lt;p id=&quot;1DJ2&quot;&gt;Сигнатура первого метода будет: &lt;code&gt;SaySomething(string, int)&lt;/code&gt;;&lt;br /&gt;Сигнатура второго: &lt;code&gt;SaySomething(string, int)&lt;/code&gt;. Такой код является абсолютно валидным с точки зрения компилятора и он скомпилируется без ошибок.&lt;/p&gt;
  &lt;p id=&quot;yYa5&quot;&gt;Параметры методов - это переменные, которые будут содержать данные, передаваемые методу при его вызове.&lt;/p&gt;
  &lt;pre id=&quot;Jsux&quot;&gt;типМетода ИмяМетода (типПараметра1 параметр1, типПараметра2 параметр2, ...)
{
    // действия метода
}&lt;/pre&gt;
  &lt;p id=&quot;5Pt4&quot;&gt;Аргументы - это выражения, значения которых используются для передачи при вызове метода.&lt;/p&gt;
  &lt;pre id=&quot;5IS2&quot; data-lang=&quot;clike&quot;&gt;void PrintHello(string name)
{
    Console.WriteLine($&amp;quot;Hello, {name}&amp;quot;);
} 

PrintHello(&amp;quot;Artem&amp;quot;);&lt;/pre&gt;
  &lt;p id=&quot;hJ8F&quot;&gt;В данном примере метод &lt;code&gt;PrintHello&lt;/code&gt; принимает 1 параметр &lt;code&gt;name&lt;/code&gt; типа &lt;code&gt;string&lt;/code&gt;, при вызове &lt;code&gt;PrintHello&lt;/code&gt; параметру &lt;code&gt;name&lt;/code&gt; передается аргумент, в данном случае строка &lt;code&gt;&amp;quot;Artem&amp;quot;&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;mM4A&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;WyKG&quot;&gt;Передача по значению&lt;/h3&gt;
  &lt;p id=&quot;ktRb&quot;&gt;Аргементы в C# могут передаваться либо по значению, либо по ссылке. По умолчанию все аргументы передаются в методы по значению. Передача по значению означает &lt;strong&gt;передачу не самой переменной, а ее копии &lt;/strong&gt;в метод.&lt;br /&gt;Предположим, мы имеем метод &lt;code&gt;Sum&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;oOIC&quot; data-lang=&quot;clike&quot;&gt;public class SomeClass
{
    public static void Sum(int a)
    {
        a = a + 3;
    }
}&lt;/pre&gt;
  &lt;p id=&quot;W8qf&quot;&gt;На первый взгляд может показаться, что данный метод увеличивает значение переданной переменной на 3. На деле же, прогнав этот метод, мы получим иной результат:&lt;/p&gt;
  &lt;pre id=&quot;ODrv&quot; data-lang=&quot;clike&quot;&gt;int a = 5;
SomeClass.Sum(a); // Ожидаем, что значение a увеличится на 3
Console.WriteLine(a); // Получаем иной результат, вывод: 5&lt;/pre&gt;
  &lt;p id=&quot;htkh&quot;&gt;Такой результат связан с тем, что все аргументы передаются по значению, т.е. в методы передаются копии переменных и внутри метода происходит работа с локальной копией!&lt;/p&gt;
  &lt;p id=&quot;TdLx&quot;&gt;При передаче же переменной ссылочного типа по значению &lt;strong&gt;будет копироваться не сам объект, а ссылка&lt;/strong&gt;, поэтому изменения производимые с объектом по копии ссылки будут отражены на объекте)&lt;/p&gt;
  &lt;pre id=&quot;4Bco&quot; data-lang=&quot;clike&quot;&gt;class Robot
{
    public int Brain { get; set; }

    public static void Changer(Robot bot)
    {
        bot.Brain = 10;
    }
}&lt;/pre&gt;
  &lt;pre id=&quot;HLtu&quot; data-lang=&quot;clike&quot;&gt;var bot = new Robot(); // Создаем объект класса Robot
bot.Brain = 3; // Присваиваем свойству значение 3
Robot.Changer(bot);
Console.WriteLine(bot.Brain); // Вывод: 10&lt;/pre&gt;
  &lt;p id=&quot;6hzg&quot;&gt;Но стоит отметить, что при изменении ссылки параметра внутри метода при передаче ссылочного типа по значению, данное изменение никак не затронет объект передаваемой переменной.&lt;/p&gt;
  &lt;pre id=&quot;xcsb&quot; data-lang=&quot;clike&quot;&gt;class Robot
{
    public int Brain { get; set; }

    public static void Changer(Robot bot)
    {
        bot = new Robot();
        bot.Brain = 5;
    }
}&lt;/pre&gt;
  &lt;pre id=&quot;vbJb&quot; data-lang=&quot;clike&quot;&gt;var bender = new Robot(); // Создаем объект класса Robot
bender.Brain = 3; // Присваиваем свойству значение 3
Robot.Changer(bender);
Console.WriteLine(bender.Brain); // Вывод: 3&lt;/pre&gt;
  &lt;p id=&quot;gF9e&quot;&gt;В классе Robot имеем метод Changer, который принимает значение ссылки на экземпляр класса Robot в переменную bot и присваивает ей ссылку на новый экземпляр. &lt;br /&gt;Здесь при копировании работает такая же логика как с типами значений: наш объект класса &lt;code&gt;Robot&lt;/code&gt; хранится где-то в куче, переменная &lt;code&gt;bender&lt;/code&gt; же на стеке хранит ссылку на этот объект, а при передаче переменной в качестве аргумента в методе работа будет происходить уже с переменной &lt;code&gt;bot&lt;/code&gt; метода, которая содержит копию ссылки, поэтому изменение ссылки у данной переменной ни к каким изменениям объекта не приведет) &lt;/p&gt;
  &lt;p id=&quot;KdRR&quot;&gt;Другими словами, при передаче переменной ссылочного типа по значению мы можем менять состояние объекта по данной ссылке, но не можем менять объект, на который указывает ссылка.&lt;/p&gt;
  &lt;p id=&quot;R8yV&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;BSJL&quot;&gt;Передача по ссылке и модификаторы параметров&lt;/h3&gt;
  &lt;p id=&quot;Kyg5&quot;&gt;Для изменения стандартной передачи аргументов по значению используются модификаторы параметров методов, с помощью которых можно управлять способами передачи аргументов. Существует 4 модификатора: &lt;code&gt;ref&lt;/code&gt;, &lt;code&gt;out&lt;/code&gt;, &lt;code&gt;in&lt;/code&gt;, &lt;code&gt;params&lt;/code&gt;. Сейчас мы про каждый из них поговорим.&lt;/p&gt;
  &lt;h3 id=&quot;u56S&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;PAfy&quot;&gt;Ссылочные параметры, модификатор ref&lt;/h3&gt;
  &lt;p id=&quot;TRsA&quot;&gt;Для передачи по ссылке можно использовать модификатор &lt;code&gt;ref&lt;/code&gt; (от reference) перед типом параметра в сигнатуре метода, а также перед передаваемым аргументом при вызове метода:&lt;/p&gt;
  &lt;pre id=&quot;OQil&quot; data-lang=&quot;clike&quot;&gt;public static void Swap(ref int a, ref int b)
{
    //вариант без создания 3-й переменной:)
    a = a + b;
    b = a - b;
    a = a - b;
}

int a = 1;
int b = 2;
Console.WriteLine($&amp;quot;a={a} b={b}&amp;quot;); //a=1 b=2
Swap(ref a, ref b);
Console.WriteLine($&amp;quot;a={a} b={b}&amp;quot;); //a=2 b=1&lt;/pre&gt;
  &lt;p id=&quot;l7ef&quot;&gt;Таким образом при передаче по ссылке все изменения над данными переменной, производимые внутри метода, будут отражены и в переменной в вызывающем коде (т.к. копирования данных не происходит и мы работаем с данными по ссылке, указывающей на ту же область памяти).&lt;/p&gt;
  &lt;p id=&quot;w4Y5&quot;&gt;При передаче переменной ссылочного типа по ссылке в параметре метода у нас по-сути создастся новая ссылка на память, где лежит ссылка на какой-то объект заданного ссылочного типа, т.е. будет ссылка на ссылку. Таким образом мы можем менять, как состояние объекта по передаваемой ссылке, так и объект, на который указывает данная ссылка.&lt;/p&gt;
  &lt;pre id=&quot;3HMH&quot; data-lang=&quot;clike&quot;&gt;class Robot
{
    public int Brain { get; set; }

    public static void Changer(ref Robot bot)
    {
        bot = new Robot();
        bot.Brain = 5;
    }
}

var bender = new Robot(); // Создаем объект класса Robot
bender.Brain = 3;
Robot.Changer(ref bender); // передаем ссылочный тип по ссылке в метод, где будет изменена ссылка
Console.WriteLine(bender.Brain); // Вывод: 5&lt;/pre&gt;
  &lt;p id=&quot;rxse&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;5ZG5&quot;&gt;Выходные параметры, модификатор out&lt;/h3&gt;
  &lt;p id=&quot;Cgoj&quot;&gt;Также для передачи по ссылке можно использовать модификатор &lt;code&gt;out&lt;/code&gt;. Он похож по функционалу на модификатор &lt;code&gt;ref&lt;/code&gt;, за исключением того, что &lt;code&gt;ref&lt;/code&gt; обязывает проинициализировать переменную перед ее передачей, если же передаваемый аргумент не будет приоинциализирован, компилятор выдаст ошибку. &lt;br /&gt;При использовании же модификатора &lt;code&gt;out&lt;/code&gt; компилятор позволяет передавать неинициализированные переменные, которые должны быть проинициализированы внутри метода. Это связано с тем, что выходному параметру в методе обязательно должно быть выполнено присваивание. &lt;/p&gt;
  &lt;pre id=&quot;eGCA&quot; data-lang=&quot;clike&quot;&gt;public static void Sum(int a, int b, out int sum)
{
    sum = a + b;
}&lt;/pre&gt;
  &lt;p id=&quot;uGdI&quot;&gt;Кроме того, передаваемые аргументы в выходные параметры не обязательно объявлять до вызова метода, это можно сделать прямо внутри метода:)&lt;/p&gt;
  &lt;pre id=&quot;dP7b&quot; data-lang=&quot;clike&quot;&gt;Sum(1, 2, out int result);
Console.WriteLine(result); // 3&lt;/pre&gt;
  &lt;p id=&quot;7GVO&quot;&gt;&lt;em&gt;Но зачем использовать выходной параметр для возвращения обычной суммы, можно же вернуть результат через &lt;code&gt;return&lt;/code&gt;?!&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;LKBS&quot;&gt;Да, такой пример с суммой носит лишь демонстрационный характер для понимания, как работать с выходными параметрам, и в данном случае в реальной ситуации лучше было бы просто вернуть значение через &lt;code&gt;return&lt;/code&gt;. &lt;br /&gt;Реальную же пользу от модификатора &lt;code&gt;out&lt;/code&gt; мы можем получить, когда из одного метода необходимо получить несколько выходных значений. Например, вычисляем в методе периметр и площадь прямоугольника:&lt;/p&gt;
  &lt;pre id=&quot;sf0g&quot; data-lang=&quot;clike&quot;&gt;void GetRectangleData(int width, int height, out int area, out int perimetr)
{
    area = width * height; 
    perimetr = (width + height) * 2;
} 

GetRectangleData(1, 2, out int area, out int perimetr); 
Console.WriteLine($&amp;quot;area = {area}, perimetr = {perimetr}&amp;quot;); // area = 2, perimetr = 6&lt;/pre&gt;
  &lt;p id=&quot;TL96&quot;&gt;Также преимуществом &lt;code&gt;out&lt;/code&gt; является использование &lt;code&gt;TryGet&lt;/code&gt; паттерна. Такой подход используется для получения некоторого значения в виде &lt;code&gt;out&lt;/code&gt; параметра и возврата &lt;code&gt;bool&lt;/code&gt; (если значение не было получено, вернется false, если получено - &lt;code&gt;true&lt;/code&gt;). &lt;/p&gt;
  &lt;p id=&quot;Zle0&quot;&gt;Рассмотрим на примере методов &lt;code&gt;Parse&lt;/code&gt; и &lt;code&gt;TryParse&lt;/code&gt; у типа &lt;code&gt;int&lt;/code&gt;. Метод &lt;code&gt;int.Parse()&lt;/code&gt; используется для преобразования строки в число типа &lt;code&gt;int&lt;/code&gt;. В случае, если число неудачи в преобразовании будет выброшено исключение (об исключениях мы уже скоро поговорим в следующей статье). Метод же &lt;code&gt;TryParse&lt;/code&gt; не выбрасывает исключения в таком случае, а возвращает &lt;code&gt;false&lt;/code&gt; и эту ситуацию можно обработать в условной конструкции. Это довольно упрощает код и не несет дополнительных расходов для обработки всех ситуаций.&lt;/p&gt;
  &lt;pre id=&quot;UKKn&quot; data-lang=&quot;clike&quot;&gt;int a = int.Parse(&amp;quot;1&amp;quot;); // a = 1
a = int.Parse(&amp;quot;aaa&amp;quot;); //FormatException

if (int.TryParse(&amp;quot;15&amp;quot;, out int b))
{
    Console.WriteLine($&amp;quot;b = {b}&amp;quot;);
}
else
{
    Console.WriteLine(&amp;quot;Wow, this is not number&amp;quot;);
}&lt;/pre&gt;
  &lt;h3 id=&quot;dD8l&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;OxBQ&quot;&gt;Входные параметры, модификатор in&lt;/h3&gt;
  &lt;p id=&quot;9Q5t&quot;&gt;Модификатор &lt;code&gt;in&lt;/code&gt; позволяет передавать значение по ссылке, но запрещает его модификацию внутри метода. Это может быть полезно, когда мы хотим снизить потенциальные расходы на память, передав ссылку вместо создания копии, но мы хотим указать, что данное значение не должно быть изменено внутри метода.&lt;/p&gt;
  &lt;pre id=&quot;EJ4e&quot; data-lang=&quot;clike&quot;&gt;public static int Sum(in int a, in int b)
{
    int sum = a + b;
    return sum;
}

int a = 5;
int b = 4;
int c = Sum(a, b);
Console.WriteLine(c);&lt;/pre&gt;
  &lt;p id=&quot;zcaH&quot;&gt;При попытке изменить параметр с модификатором &lt;code&gt;in&lt;/code&gt; мы получим ошибку.&lt;/p&gt;
  &lt;h3 id=&quot;VOAd&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;4211&quot;&gt;&lt;strong&gt;Модификатор params&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;W551&quot;&gt;Что если мы встретимся с необходимостью передавать неопределенное число аргументов в метод? Для решения таких проблем существует модификатор &lt;code&gt;params&lt;/code&gt;. Он указывает, что в качестве аргументов будет передано произвольное число аргументов или не передано вообще ничего:&lt;/p&gt;
  &lt;pre id=&quot;lAng&quot;&gt;МодификаторДоступа ВозвращаемыйТип ИмяМетода(params ИмяТипа[] ИмяПеременной)
{
    тело метода
}&lt;/pre&gt;
  &lt;p id=&quot;9pLy&quot;&gt;При использовании &lt;code&gt;params&lt;/code&gt; происходит следующее: метод принимает произвольное число аргументов одного типа (в том числе и 0) или одномерный массив элементов указанного типа, в случае передачи произвольного числа аргументов, они преобразуются к одномерному массиву соответствующего типа. При вызове такого метода мы просто передаем в него в качестве аргументов переменные заданного типа через запятую.&lt;/p&gt;
  &lt;p id=&quot;gR5v&quot;&gt;Стоит запомнить ряд требований:&lt;/p&gt;
  &lt;ul id=&quot;Cd2P&quot;&gt;
    &lt;li id=&quot;lRN5&quot;&gt;в сигнатуре метода может присутствовать только одно ключевое слово params;&lt;/li&gt;
    &lt;li id=&quot;0YEx&quot;&gt;если метод помимо params параметра имеет какие-либо другие параметры, то параметр с ключевым словом params указывается последним.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;VtvU&quot;&gt;Примеры методов с использованием данного ключевого слова:&lt;/p&gt;
  &lt;pre id=&quot;8Lop&quot; data-lang=&quot;clike&quot;&gt;class SomeClass
{
    public void DoSomething1(params int[] c)
    {
        for (int i = 0; i &amp;lt; c.Length; i++)
            Console.WriteLine(c[i]);
    }
    
    public void DoSomething2(int a, params int[] b)
    {
    }
    
    public void DoSomething3(string a, int b, params int[] c)
    {
    }
}&lt;/pre&gt;
  &lt;p id=&quot;o3gx&quot;&gt;Тогда валидными конструкциями вызова данных методов будут:&lt;/p&gt;
  &lt;pre id=&quot;xZYs&quot; data-lang=&quot;clike&quot;&gt;SomeClass a = new SomeClass(); // Создали экземпляр класса
int[] array = { 1, 2, 3 }; // Создали массив из трех элементов

a.DoSomething1(); // params может принимать 0 аргументов!
a.DoSomething1(1);
a.DoSomething1(1, 2, 3, 4, 5);
a.DoSomething1(5, 2);
a.DoSomething1(array);

a.DoSomething2(1); // В данном методе обязан быть хотя бы 1 целочисленный
                   // аргумент, поскольку помимо переменного числа
                   // аргументов указан обязательный целочисленый параметр
a.DoSomething2(2, array);
                   
a.DoSomething3(&amp;quot;hi&amp;quot;, 1); // Аналогично методу DoSomething2                 
a.DoSomething3(&amp;quot;bye&amp;quot;, 2, 3, 4, 5, 6);
a.DoSomething3(&amp;quot;hey!&amp;quot;, 5, array);&lt;/pre&gt;
  &lt;p id=&quot;uhoZ&quot;&gt;Здесь стоит отметить, что методы, имеющие массив в качестве параметра и методы, имеющие параметр, помеченный ключевым словом &lt;code&gt;params&lt;/code&gt; – это разные случаи. Первый метод обязательно принимает аргумент массив, в то время как второй – может не принимать аргументов вовсе:&lt;/p&gt;
  &lt;pre id=&quot;0v7x&quot; data-lang=&quot;clike&quot;&gt;public void FirstMethod(int[] array) // При вызове обязательно принимает
{                                    // аргумент – массив
}

public void SecondMethod(params int[] array) // При вызове может не
{                                            // принимать ничего
}&lt;/pre&gt;
  &lt;p id=&quot;Y8ZF&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;VLpx&quot;&gt;Необязательные параметры&lt;/h3&gt;
  &lt;p id=&quot;HkcH&quot;&gt;В отличие от &amp;quot;конкурента&amp;quot; Java в C# есть возможность делать &amp;quot;необязательные параметры&amp;quot;, которые также еще называют параметрами со значениями по умолчанию. Это позволяет задать некоторое стандартное значение параметра, и при вызове метода не передавать в данный параметр аргумент, при условии, что подходит указанное значение.&lt;/p&gt;
  &lt;blockquote id=&quot;HB5u&quot;&gt;И это просто замечательно, как эти жабисты существуют без параметров по умолчанию, я не понимаю:)&lt;/blockquote&gt;
  &lt;p id=&quot;BWi3&quot;&gt;Важно отметить, что все необязательные параметры должны быть указаны в конце сигнатуры после всех обычных параметров. Пример:&lt;/p&gt;
  &lt;pre id=&quot;uI1O&quot; data-lang=&quot;clike&quot;&gt;static void PrintRobotInfo(string msg, int age = 500, string name = &amp;quot;Bender&amp;quot;)
{
    Console.WriteLine($&amp;quot;I&amp;#x27;m {name}, {age} years&amp;quot;);
    Console.WriteLine(msg);
}

PrintRobotInfo(&amp;quot;Kill all humans&amp;quot;, 100);
// I&amp;#x27;m Bender, 100 years
// Kill all humans&lt;/pre&gt;
  &lt;p id=&quot;1gt9&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;Cjwz&quot;&gt;Как-то так) &lt;/p&gt;
  &lt;p id=&quot;eNfU&quot;&gt;В следующей статье речь пойдет об исключениях и программном стеке вызова методов. Подписывайтесь на наш &lt;a href=&quot;https://t.me/serious_seesharp&quot; target=&quot;_blank&quot;&gt;канал&lt;/a&gt; в тг, там публикуются все статьи, материалы и прочие ништяки. Вступайте в наш &lt;a href=&quot;https://t.me/notserious_seesharp_chat&quot; target=&quot;_blank&quot;&gt;чат&lt;/a&gt; для обсуждения и помощи, там пока ничего не происходит)&lt;/p&gt;
  &lt;p id=&quot;639Y&quot;&gt;&lt;br /&gt;p.s. Сейчас вылетел из графика из-за загруженности, пишу по возможности, все будет как только, так сразу. Накидывайте лайки на посты в телеге, буду видеть, что мой материал вам интересен, буду стараться делать быстрее.&lt;/p&gt;

</content></entry><entry><id>notserious_seesharp:data-structures-basic</id><link rel="alternate" type="text/html" href="https://teletype.in/@notserious_seesharp/data-structures-basic?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=notserious_seesharp"></link><title>Структуры данных в C#: массивы и коллекции</title><published>2022-11-03T15:50:15.363Z</published><updated>2024-10-01T19:04:23.439Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/2e/38/2e38e133-55c8-44e2-9563-a929b7c85369.png"></media:thumbnail><category term="osnovy-c" label="Основы C#"></category><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/3e/cb/3ecb6a79-c9d9-465b-96c6-85def25d6903.png&quot;&gt;Привет! В этой статье речь пойдет о такой неотъемлемой вещи в программировании как структуры данных.</summary><content type="html">
  &lt;p id=&quot;sPid&quot;&gt;Привет! В этой статье речь пойдет о такой неотъемлемой вещи в программировании как структуры данных.&lt;/p&gt;
  &lt;figure id=&quot;rI42&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/3e/cb/3ecb6a79-c9d9-465b-96c6-85def25d6903.png&quot; width=&quot;1200&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;WEjv&quot;&gt;Структуры данных&lt;/h3&gt;
  &lt;p id=&quot;NNqo&quot;&gt;Структура данных - это некий способ организации информации в памяти определенным образом для эффективного хранения этой информации и извлечения.&lt;/p&gt;
  &lt;p id=&quot;oJWq&quot;&gt;Структуры данных используются так или иначе в каждом проекте. Они позволяют упорядочивать, искать, анализировать и использовать данные с применением алгоритмов программирования. Для каждой структуры данных — свои алгоритмы.&lt;/p&gt;
  &lt;p id=&quot;al9g&quot;&gt;Типы структур:&lt;/p&gt;
  &lt;ul id=&quot;Aazw&quot;&gt;
    &lt;li id=&quot;Qbcm&quot;&gt;Линейные&lt;strong&gt; - с&lt;/strong&gt;труктуры данных, в которых элементы расположены последовательно, где каждый элемент прикреплен к своему предыдущему и следующему соседним элементам.&lt;br /&gt;Линейные структуры бывают статическими и динамическими:&lt;br /&gt;&lt;strong&gt;🞄    &lt;/strong&gt;Статическая структура данных имеет фиксированный размер памяти                                                                            (например, массивы);&lt;br /&gt;&lt;strong&gt;🞄    &lt;/strong&gt;В динамической структуре данных размер не фиксирован. Он может обновляться во время выполнения. (списки, очереди, стек)&lt;/li&gt;
    &lt;li id=&quot;cMGC&quot;&gt;Нелинейные - структуры данных, в которых элементы данных расположены не последовательно. В нелинейной структуре данных мы не можем обойти все элементы только за один проход. (графы, деревья)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;aqwm&quot;&gt;Как видите, существует довольно много структур со своими особенностями устройства, и задача программиста для эффективной работы с данными — правильно выбирать из уже имеющихся либо писать свои.&lt;/p&gt;
  &lt;p id=&quot;lkSl&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;qupf&quot;&gt;Массивы&lt;/h3&gt;
  &lt;p id=&quot;0FA0&quot;&gt;Массив – это простейшая структура данных, позволяющая последовательно хранить набор однотипных данных. Массив в C# является ссылочным типом данных, т.е. мы создаем некоторую переменную, ссылающуюся на какую-то область в куче, где последовательно хранится содержимое нашего массива. Объявление массива схоже с обычным объявлением переменной, разве что после типа ставятся квадратные скобки:&lt;/p&gt;
  &lt;pre id=&quot;zZaJ&quot;&gt;Тип[] имяПеременной;&lt;/pre&gt;
  &lt;p id=&quot;70Ps&quot;&gt;Так, например, объявляется массив целых чисел:&lt;/p&gt;
  &lt;pre id=&quot;i4Dj&quot; data-lang=&quot;clike&quot;&gt;int[] array;&lt;/pre&gt;
  &lt;p id=&quot;VnxT&quot;&gt;Каждое отдельное значение набора обычно именуют элементом массива. Размером (или длиной) массива называют количество его элементов, это значение неизменяемо! Каждый элемент массива идентифицируется по индексу. Индекс – это целое число из заданного диапазона, начинающегося с нуля и заканчивающегося размером массива минус один, так как индексацию в C# также как и во многих других языках программирования принято начинать с 0.&lt;/p&gt;
  &lt;figure id=&quot;fnrZ&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/39/e6/39e60bf9-5188-4e27-9d0d-1ce80b0d1799.png&quot; width=&quot;508&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;lKRU&quot;&gt;Разберемся с каждым определением на примерах!&lt;/p&gt;
  &lt;p id=&quot;kXaI&quot;&gt;Для инициализации массивов мы все так же пользуемся оператором &lt;code&gt;new&lt;/code&gt;, как  и при инициализации других ссылочных типов:&lt;/p&gt;
  &lt;pre id=&quot;Xr5K&quot;&gt;Тип[] имяПеременной = new Тип[размерМассива];&lt;/pre&gt;
  &lt;p id=&quot;eUad&quot;&gt;Например, объявим и проинициализируем массив целочисленных значений на 7 элементов:&lt;/p&gt;
  &lt;pre id=&quot;pMpE&quot; data-lang=&quot;clike&quot;&gt;int[] array = new int[7];&lt;/pre&gt;
  &lt;p id=&quot;xurL&quot;&gt;Но на данный момент у нас имеется массив элементов, которые мы никак не задавали (не проинициализировали). А как вы помните, все не проинициализированные элементы вручную инициализируются своими значениями по умолчанию, соответствующие типу, и с массивами это работает аналогично. Т.е. в данном случае мы получим набор из 7 элементов, равных 0. &lt;/p&gt;
  &lt;p id=&quot;nYC2&quot;&gt;Другим способом инициализации является использование инициализаторов объекта, о которых мы говорили в &lt;a href=&quot;/@notserious_seesharp/classes_basic#tZMv&quot;&gt;одной из прошлых статей&lt;/a&gt;:&lt;/p&gt;
  &lt;pre id=&quot;GLss&quot; data-lang=&quot;clike&quot;&gt;int[] firstArray = new int[7] { 1, 2, 3, 4, 5, 6, 7 }; // массив из 7 элементов
int[] secondArray = new int[] { 1, 2, 3 }; // массив из 3 элементов
int[] thirdArray = new [] { 1, 2, 3, 4, 5 }; // массив из 5 элементов
int[] fourthArray = { 1, 2, 3, 4 }; // массив из 4 элементов&lt;/pre&gt;
  &lt;p id=&quot;jTFm&quot;&gt;Все четыре конструкции валидны и приводят к созданию массивов, наполненных нужными нам значениями.&lt;/p&gt;
  &lt;p id=&quot;JEip&quot;&gt;Для работы с элементами массива нам и понадобятся индексы! Массив – это линейная структура данных, все элементы набора расположены в памяти последовательно, каждый в своей ячейке памяти, и данная область памяти является непрерывной.&lt;/p&gt;
  &lt;p id=&quot;gMIi&quot;&gt;Использование индексов – это быстрый способ обратиться к конкретному элементу последовательности. Индексация массива начинается с 0, поэтому чтобы обратиться к первому элементу массива, нам необходимо использовать индекс 0, а для обращения к четвертому элементу – индекс 3. Для обращения к конкретному элементу необходимо после имени переменной массива в квадратных скобках указать индекс интересующего нас элемента:&lt;/p&gt;
  &lt;pre id=&quot;iDsb&quot; data-lang=&quot;clike&quot;&gt;int[] array = { 101, 3, 42, 5 }; // создали массив из 4 элементов
Console.WriteLine(array[0]); // Выводим первый элемент массива: 101
Console.WriteLine(array[1]); // Выводим второй элемент массива: 3

array[1] = 121;              // Заменяем значение второго элемента массива
Console.WriteLine(array[1]); // Выводим второй элемент массива: 121&lt;/pre&gt;
  &lt;p id=&quot;jruO&quot;&gt;Запомните! Обратиться мы можем только к существующим элементам, т.е. наш массив должен существовать, а индекс, по которому мы обращаемся, должен быть в пределах от нуля до размер массива минус 1 [0, n - 1]:&lt;/p&gt;
  &lt;pre id=&quot;sHIU&quot;&gt;int[] firstArray;
Console.WriteLine(firstArray[0]); // Ошибка компиляции! Использование
                                  // неинициализированной переменной!
int[] secondArray = {1, 2, 3, 4};
Console.WriteLine(secondArray[4]); // Исключение во время работы программы!
                                   // Обращение к несуществующему элементу!&lt;/pre&gt;
  &lt;h3 id=&quot;qa3i&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;YPiE&quot;&gt;Операции с массивами&lt;/h3&gt;
  &lt;p id=&quot;37uZ&quot;&gt;Массивы в C# представляют собой реальные объекты, а не просто адресуемые области непрерывной памяти (как это сделано в C++, например). &lt;code&gt;Array&lt;/code&gt; является абстрактным базовым классом для всех типов массивов и содержит ряд методов и свойств для работы с массивами.&lt;/p&gt;
  &lt;p id=&quot;nJ0j&quot;&gt;Например, с помощью свойства &lt;code&gt;Length&lt;/code&gt; можно получить длину массива, обратившись к нему по имени массива:&lt;/p&gt;
  &lt;pre id=&quot;k1zQ&quot; data-lang=&quot;clike&quot;&gt;int[] arr = { 21, 42, 53, 103 };
Console.WriteLine(arr.Length); // Вывод: 4&lt;/pre&gt;
  &lt;p id=&quot;c0IW&quot;&gt;Класс &lt;code&gt;Array&lt;/code&gt; также содержит множество статических методов (не зависящих от состояния массивов, от количества элементов в них) для работы с данными массивов:&lt;/p&gt;
  &lt;ul id=&quot;VVfp&quot;&gt;
    &lt;li id=&quot;02IW&quot;&gt;метод &lt;code&gt;Clear&lt;/code&gt; очищает массив, устанавливая всем элементам значения по умолчанию (в качестве аргументов принимает массив, начальный индекс диапазона, с которого происходит очистка, а также число элементов, подлежащих очистке);&lt;/li&gt;
    &lt;li id=&quot;bomV&quot;&gt;&lt;code&gt;Reverse&lt;/code&gt; располагает элементы в массиве в обратном порядке (в качестве аргумента принимает массив, который мы хотим развернуть);&lt;/li&gt;
    &lt;li id=&quot;xjiP&quot;&gt;&lt;code&gt;IndexOf&lt;/code&gt; возвращает индекс переданного в качестве аргумента элемента, если он существует, или возвращающий -1, если такого элемента нет (в качестве аргументов принимает массив и элемент, индекс которого в данном массиве мы хотим узнать).&lt;/li&gt;
    &lt;li id=&quot;Bg0D&quot;&gt;&lt;code&gt;Sort&lt;/code&gt; сортирует элементы массива.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;sFJP&quot;&gt;Array содержит ряд других методов и свойств, подробнее с которыми можно ознакомиться с помощью официальной &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/api/system.array&quot; target=&quot;_blank&quot;&gt;документации&lt;/a&gt; при решении конкретных задач.&lt;/p&gt;
  &lt;pre id=&quot;0eAR&quot; data-lang=&quot;clike&quot;&gt;int[] arr = { 21, 42, 53, 103 };
Console.WriteLine(arr.Length); // Вывод: 4

Console.WriteLine(Array.IndexOf(arr, 42)); // Вывод: 1
Console.WriteLine(Array.IndexOf(arr, 103)); // Вывод: 3
Console.WriteLine(Array.IndexOf(arr, 2)); // Вывод: -1

Array.Reverse(arr);
Console.WriteLine(arr[0]); // Вывод: 103

Array.Clear(arr, 0, 4); // Очищаем значениями по умолчанию 4 элемента,
                        // начиная с первого (по индексу ноль)
Console.WriteLine(arr[0]); // Вывод: 0&lt;/pre&gt;
  &lt;p id=&quot;upNo&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;BNnS&quot;&gt;Многомерные массивы&lt;/h3&gt;
  &lt;p id=&quot;fuzT&quot;&gt;Размерность массива (ранг) – это количество индексов, необходимое для однозначного определения каждого элемента массива. Размерность массива можно определить с помощью свойства &lt;code&gt;Rank&lt;/code&gt;, обратившись к нему через имя переменной массива и оператора точки:&lt;/p&gt;
  &lt;pre id=&quot;W1vS&quot; data-lang=&quot;clike&quot;&gt;int[] array = { 1, 2, 3 };
Console.WriteLine(array.Rank); // Вывод: 1&lt;/pre&gt;
  &lt;p id=&quot;Nqbz&quot;&gt;В предыдущих примерах мы разбирали только одномерные массивы – массивы, которые можно представить в виде ряда:&lt;/p&gt;
  &lt;pre id=&quot;8GAo&quot; data-lang=&quot;clike&quot;&gt;int[] array = new int[6] { 0, 1, 2, 3, 4, 5 };&lt;/pre&gt;
  &lt;figure id=&quot;Yuuf&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/8f/2a/8f2a4a2f-60c3-437c-9033-a83cbc476f82.png&quot; width=&quot;279&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;2hBu&quot;&gt;Для обращения к элементу такого массива нам достаточного одного индекса.&lt;/p&gt;
  &lt;p id=&quot;MczO&quot;&gt;Размерность массивов может быть больше 1. Для объявления таких массивов в квадратных скобках необходимо запятой как бы разделять измерения. Например, объявление и инициализация двумерного массива может выглядеть так:&lt;/p&gt;
  &lt;pre id=&quot;14yW&quot; data-lang=&quot;clike&quot;&gt;int[,] array = new int[2, 3];
Console.WriteLine(array.Rank); //Вывод: 2&lt;/pre&gt;
  &lt;p id=&quot;WG9K&quot;&gt;Таким нехитрым действием мы создали двумерный массив интов 2 на 3. (Грубо говоря, 2 массива по 3 элемента в каждом).&lt;/p&gt;
  &lt;p id=&quot;bqkW&quot;&gt;Инициализатор будет работать аналогичным образом:&lt;/p&gt;
  &lt;pre id=&quot;FI9R&quot; data-lang=&quot;clike&quot;&gt;int[,] firstArray= new int[2, 3] { { 0, 1, 2 }, { 3, 4, 5 } };
int[,] secondArray= new int[,] { { 0, 1, 2 }, { 3, 4, 5 } };
int[,] thirdArray = new [,] { { 0, 1, 2 }, { 3, 4, 5 } };
int[,] fourthArray = { { 0, 1, 2 }, { 3, 4, 5 } };&lt;/pre&gt;
  &lt;p id=&quot;kRp3&quot;&gt;Такой двумерный массив можно представить в виде таблицы – сочетаний строк и столбцов:&lt;/p&gt;
  &lt;pre id=&quot;eqLM&quot; data-lang=&quot;clike&quot;&gt;int[,] array = { { 0, 1, 2 }, { 3, 4, 5 } };&lt;/pre&gt;
  &lt;figure id=&quot;ZM9F&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/3e/91/3e91723e-300f-4792-9891-e7274b5c05da.png&quot; width=&quot;230&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;06fb&quot;&gt;Для обращения к элементу такого массива нам уже необходимо два индекса, как если бы мы обращались к ячейке таблицы:&lt;/p&gt;
  &lt;pre id=&quot;ROYR&quot; data-lang=&quot;clike&quot;&gt;Console.WriteLine(array[0, 0]); // Вывод: 0
Console.WriteLine(array[0, 1]); // Вывод: 1
Console.WriteLine(array[0, 2]); // Вывод: 2
Console.WriteLine(array[1, 0]); // Вывод: 3
Console.WriteLine(array[1, 1]); // Вывод: 4
Console.WriteLine(array[1, 2]); // Вывод: 5&lt;/pre&gt;
  &lt;p id=&quot;NJ0n&quot;&gt;Таким образом мы можем определять трехмерные, четырехмерные и даже n-мерные массивы, но на практике чаще всего встречаются одномерные и двумерные.&lt;/p&gt;
  &lt;p id=&quot;QwrA&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;nwYM&quot;&gt;Массивы массивов&lt;/h3&gt;
  &lt;p id=&quot;4Zl5&quot;&gt;Массив массивов – это массив, который в качестве своих элементов хранит ссылки на другие массивы:&lt;/p&gt;
  &lt;pre id=&quot;UTCu&quot; data-lang=&quot;clike&quot;&gt;int[][] array = new int[10][]; // объявление и инициализация массива
                               // из десяти массивов&lt;/pre&gt;
  &lt;p id=&quot;APpB&quot;&gt;В результате работы кода выше будет создан массив, содержащий ссылки на 10 массивов. Эти ссылки необходимо отдельно проинициализировать:&lt;/p&gt;
  &lt;pre id=&quot;23l8&quot; data-lang=&quot;clike&quot;&gt;for(int i = 0; i &amp;lt; array.Length; i++)
{
    array[i] = new int[10];
}&lt;/pre&gt;
  &lt;p id=&quot;GDiM&quot;&gt;С помощью цикла for мы проходимся по нашему массиву, инициализируя каждую ссылку массивом целочисленных значений размером 10.&lt;/p&gt;
  &lt;p id=&quot;37Ha&quot;&gt;Нередко массив массивов называют ступенчатым (или зубчатым) массивом. Связано это с тем, что при инициализации подмассивов мы можем задавать им различные размеры:&lt;/p&gt;
  &lt;pre id=&quot;6yPg&quot; data-lang=&quot;clike&quot;&gt;int[][] array = new int[3][]; // Создали массив из трех массивов
array[0] = new int[3]; // Инициализируем подмассивы
array[1] = new int[5];
array[2] = new int[2];&lt;/pre&gt;
  &lt;figure id=&quot;YV1c&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/dd/0b/dd0b251d-7fd7-4b9c-b90f-ebd227bd6b75.png&quot; width=&quot;398&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;PD9n&quot;&gt;Если сравнивать зубчатые массивы с многомерными массивами, то у многомерных массивов всегда размер всех подмассивов одинаковый.&lt;/p&gt;
  &lt;p id=&quot;jnoF&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;oN53&quot;&gt;Коллекции в C#&lt;/h3&gt;
  &lt;p id=&quot;MugH&quot;&gt;Хотя базовые массивы могут быть удобными для управления небольшими объемами данных фиксированного размера, есть немало случаев, когда требуются более гибкие структуры данных, такие как динамически расширяющийся и сокращающийся контейнер, ассоциативные контейнеры с доступом к элементам по ключу и прочее)&lt;/p&gt;
  &lt;p id=&quot;EmiK&quot;&gt;Для преодоления ограничений простого массива, библиотека базовых классов .NET Core поставляются с несколькими пространствами имен, которые содержат классы коллекций. В отличие от простого массива C# классы коллекций построены с возможностью динамического изменения своих размеров по мере вставки либо удаления из них элементов.&lt;/p&gt;
  &lt;p id=&quot;YAVO&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;bggI&quot;&gt;Динамические массивы&lt;/h3&gt;
  &lt;p id=&quot;32z3&quot;&gt;Динамическим называется массив, размер которого может меняться во время исполнения программы. Мы можем решить данную задачу с обычным массивом, используя статический метод &lt;code&gt;Array.Resize&lt;/code&gt;. &lt;/p&gt;
  &lt;pre id=&quot;JIZ3&quot; data-lang=&quot;clike&quot;&gt;int[] array = new int[5] { 1, 2, 3, 4, 5 };

Array.Resize(ref array, 7);
for (int i = 0; i &amp;lt; array.Length; i++)
    Console.Write($&amp;quot;{array[i]} &amp;quot;); //1 2 3 4 5 0 0

Array.Resize(ref array, 3);
for (int i = 0; i &amp;lt; array.Length; i++)
    Console.Write($&amp;quot;{array[i]} &amp;quot;); //1 2 3&lt;/pre&gt;
  &lt;blockquote id=&quot;YINi&quot;&gt;о том, что такое &lt;code&gt;ref&lt;/code&gt;, речь пойдет в следующей небольшой статье:)&lt;/blockquote&gt;
  &lt;p id=&quot;OVwt&quot;&gt;В ходе выполнения метода, будет создан новый массив указанной нами длины, после чего все значения из старого массива будут скопированы в него (если новый массив больше, то новые элементы будут проинициализированы значениями по умолчанию, а если меньше, то количество значений просто &amp;quot;обрежется&amp;quot;).&lt;/p&gt;
  &lt;p id=&quot;40G9&quot;&gt;Но такой подход не удобно использовать, каждый раз нам приходится пересоздавать массив, когда требуется добавить новый элемент, и массив будет пересоздан на фиксированное количество элементов, которые сразу же будут проиницилизированы, что может привести к ошибкам. Для решения этих проблем в библиотеке базовых классов представлено довольно большое количество различных коллекций со своими особенностями. &lt;/p&gt;
  &lt;p id=&quot;AhM1&quot;&gt;Класс &lt;code&gt;ArrayList&lt;/code&gt; из пространства имен &lt;code&gt;System.Collections&lt;/code&gt; представляет собой коллекцию с массивом динамически изменяющегося размера.&lt;/p&gt;
  &lt;pre id=&quot;tsts&quot; data-lang=&quot;clike&quot;&gt;using System.Collections;

ArrayList arr = new ArrayList();&lt;/pre&gt;
  &lt;p id=&quot;kWS8&quot;&gt;Под капотом &lt;code&gt;ArrayList&lt;/code&gt; имеет обычный массив, но в отличие от него в работе с динамическими массивами добавляется еще одно свойство – &lt;code&gt;Capacity&lt;/code&gt; (вместимость). При создании динамического массива определенного размера, помимо самого размера определяется также значение вместимости, в соответствии с которым и выделяются необходимые объемы памяти под данную коллекцию. &lt;/p&gt;
  &lt;p id=&quot;kah1&quot;&gt;При добавлении новых элементов в коллекцию &lt;code&gt;ArrayList&lt;/code&gt; происходит проверка: не превысит ли длина массива максимально возможной, в соответствии с которой была выделена память. В случае, если выделенное место в &lt;code&gt;Capacity&lt;/code&gt; еще есть, то оно занимается новым значением. Иначе выделяется новое место в памяти под новый массив размером в два раза больше текущего, все элементы копируются в новый массив с сохранением индексов, ссылка на новый массив присваивается переменной, хранящей ссылку на старую, а старый массив становится &amp;quot;мусором&amp;quot;.&lt;/p&gt;
  &lt;figure id=&quot;yv9h&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/23/23/23237569-dcdc-4a96-bfba-17c1b0cd99ca.png&quot; width=&quot;625&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;pkSE&quot;&gt;Основные свойства и методы для работы с &lt;code&gt;ArrayList&lt;/code&gt;:&lt;/p&gt;
  &lt;ul id=&quot;UjnV&quot;&gt;
    &lt;li id=&quot;ouIa&quot;&gt;&lt;code&gt;Count&lt;/code&gt; – свойство, возвращающее количество элементов в массиве. Для обычных массивов это свойство называлось &lt;code&gt;Length&lt;/code&gt;, в динамических коллекциях стандартной библиотеки его стали именовать &lt;code&gt;Count&lt;/code&gt; для логического разделения со статическими;&lt;/li&gt;
    &lt;li id=&quot;J1kG&quot;&gt;&lt;code&gt;Capacity&lt;/code&gt; – свойство, возвращающее текущую вместимость динамического массива. Количество элементов, под которое выделена память для текущего массива, после превышения которого память под массив будет перевыделяться и массив копироваться;&lt;/li&gt;
    &lt;li id=&quot;vNNm&quot;&gt;&lt;code&gt;Add&lt;/code&gt; – метод, позволяющий добавить элемент в массив, в качестве аргумента принимает добавляемый элемент;&lt;/li&gt;
    &lt;li id=&quot;q61E&quot;&gt;&lt;code&gt;Remove&lt;/code&gt; – метод, позволяющий удалить первое вхождение элемента, переданного в качестве аргумента;&lt;/li&gt;
    &lt;li id=&quot;8sAv&quot;&gt;&lt;code&gt;RemoveAt&lt;/code&gt; – метод, позволяющий удалить элемент по индексу.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;vhVp&quot;&gt;При создании ArrayList без передачи в его конструктор размера, он не содержит никаких элементов и его &lt;code&gt;Capacity&lt;/code&gt; и &lt;code&gt;Count&lt;/code&gt; равны 0:&lt;/p&gt;
  &lt;pre id=&quot;TUqw&quot; data-lang=&quot;clike&quot;&gt;ArrayList arr = new ArrayList();
Console.WriteLine(arr.Count); // Вывод: 0 
Console.WriteLine(arr.Capacity); // Вывод: 0&lt;/pre&gt;
  &lt;p id=&quot;v2Jw&quot;&gt;Но мы можем сразу зарезервировать память под наш массив передав необходимый размер в конструктор. Таким образом размер массива по-прежнему будет равен 0, но уже будет зарегистрирована некоторая память для добавления новых элементов:&lt;/p&gt;
  &lt;pre id=&quot;n1q7&quot; data-lang=&quot;clike&quot;&gt;ArrayList arr = new ArrayList(7);
Console.WriteLine(arr.Count); // Вывод: 0 
Console.WriteLine(arr.Capacity); // Вывод: 7&lt;/pre&gt;
  &lt;p id=&quot;wBRp&quot;&gt;При добавлении новых элементов мы можем отследить, как меняется размер и вместимость массива:&lt;/p&gt;
  &lt;pre id=&quot;cx98&quot; data-lang=&quot;clike&quot;&gt;ArrayList someArray = new ArrayList(3);

someArray.Add(1);
Console.WriteLine(someArray.Count); // Вывод: 1
Console.WriteLine(someArray.Capacity); // Вывод: 3

someArray.Add(2);
someArray.Add(3);
Console.WriteLine(someArray.Count); // Вывод: 3
Console.WriteLine(someArray.Capacity); // Вывод: 3

someArray.Add(4);
Console.WriteLine(someArray.Count); // Вывод: 4
Console.WriteLine(someArray.Capacity); // Вывод: 6

someArray.Add(5);
someArray.Add(6);
Console.WriteLine(someArray.Count); // Вывод: 6
Console.WriteLine(someArray.Capacity); // Вывод: 6

someArray.Add(7);
Console.WriteLine(someArray.Count); // Вывод: 7
Console.WriteLine(someArray.Capacity); // Вывод: 12&lt;/pre&gt;
  &lt;p id=&quot;c5h1&quot;&gt;Также важно отметить, что &lt;code&gt;ArrayList&lt;/code&gt; относится к неуневерсальным коллекциям, то есть не является типобезопасным и может содержать элементы сразу нескольких разных типов, что реализуется благодаря приведению всех элементов коллекции к базовому типу Object (о приведении типов мы скоро поговорим в следующих статьях:).&lt;/p&gt;
  &lt;pre id=&quot;j4uJ&quot; data-lang=&quot;clike&quot;&gt;ArrayList arr = new ArrayList();
arr.Add(2);
arr.Add(&amp;quot;hey&amp;quot;);
arr.Add(&amp;#x27;c&amp;#x27;);
arr.Add(3.5);&lt;/pre&gt;
  &lt;p id=&quot;SWQU&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;WQkh&quot;&gt;Универсальные коллекции&lt;/h3&gt;
  &lt;p id=&quot;jlEP&quot;&gt;Хранить всё подряд в своей коллекции может быть полезно и здорово, но не безопасно и в большинстве случаев требуется точного ограничения того, что может содержаться в нашем наборе, строго типизировать его. Библиотека классов .NET предоставляет ряд универсальных классов коллекций в пространстве имен System.Collections.Generic, являющихся строго типизированными и обычно обеспечивают более высокую производительность. &lt;/p&gt;
  &lt;p id=&quot;BR9f&quot;&gt;Generics(обобщенные типы) – достаточно объемная и не самая простая тема, но для базового понимания работы коллекций сейчас в нее углубляться нам не надо. В нашем случае generic служит в качестве способа указания того, что будет храниться внутри коллекции.&lt;/p&gt;
  &lt;p id=&quot;kAts&quot;&gt;Среди основных универсальных коллекций отметим:&lt;/p&gt;
  &lt;ul id=&quot;StA7&quot;&gt;
    &lt;li id=&quot;YcJy&quot;&gt;&lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; – список, типизированный аналог неуневерсальной коллекции &lt;code&gt;ArrayList&lt;/code&gt;;&lt;/li&gt;
    &lt;li id=&quot;HZML&quot;&gt;&lt;code&gt;Dictionary&amp;lt;K, T&amp;gt;&lt;/code&gt; – словарь, типизированный аналог неуневерсального &lt;code&gt;Hashtable&lt;/code&gt;;&lt;/li&gt;
    &lt;li id=&quot;Y2Ao&quot;&gt;&lt;code&gt;Queue&amp;lt;T&amp;gt;&lt;/code&gt; – очередь, типизированный аналог неуневерсального  &lt;code&gt;Queue&lt;/code&gt;;&lt;/li&gt;
    &lt;li id=&quot;nkzf&quot;&gt;&lt;code&gt;Stack&amp;lt;T&amp;gt;&lt;/code&gt; – стек, типизированный аналог неуневерсального  &lt;code&gt;Stack&lt;/code&gt;;&lt;/li&gt;
    &lt;li id=&quot;QE6A&quot;&gt;&lt;code&gt;HashSet&amp;lt;T&amp;gt;&lt;/code&gt; – хэшсет, коллекция, содержащая элементы без повторений, хранящая их в произвольном порядке;&lt;/li&gt;
    &lt;li id=&quot;PVsO&quot;&gt;и другие...&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;hzJc&quot;&gt;Например посмотрим на пример ниже обобщенного списка значений типа int:&lt;/p&gt;
  &lt;pre id=&quot;RD1S&quot; data-lang=&quot;clike&quot;&gt;List&amp;lt;int&amp;gt; integerList = new List&amp;lt;int&amp;gt;();&lt;/pre&gt;
  &lt;p id=&quot;tZpr&quot;&gt;Данный список может содержать только целочисленные значения int, поэтому при добавлении новых элементов соответствующего типа int мы не будут тратиться дополнительные расходы на упаковку/распаковку при добавлении/извлечении элемента.&lt;/p&gt;
  &lt;p id=&quot;sm8R&quot;&gt;Внутри треугольных скобок мы указываем тип данных, который будет храниться в списке:&lt;/p&gt;
  &lt;pre id=&quot;H2Gk&quot; data-lang=&quot;clike&quot;&gt;List&amp;lt;int&amp;gt; firstList = new List&amp;lt;int&amp;gt;(); // Список целочисленных значений
List&amp;lt;string&amp;gt; secondList = new List&amp;lt;string&amp;gt;(); // Список строк
List&amp;lt;List&amp;lt;int&amp;gt;&amp;gt; thirdList = new List&amp;lt;List&amp;lt;int&amp;gt;&amp;gt;(); // Так тоже можно =)
                                           // Получаем список списков интов
                                           // подобно массиву массивов&lt;/pre&gt;
  &lt;p id=&quot;TX0s&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;AX40&quot;&gt;Перебор коллекций&lt;/h3&gt;
  &lt;p id=&quot;ptgE&quot;&gt;Мы уже знаем, что для обращения к какому-то конкретному индексу и для перебора индексируемых коллекций массивов/списков можно использовать обычный цикл &lt;code&gt;for&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;rjFg&quot; data-lang=&quot;clike&quot;&gt;List&amp;lt;int&amp;gt; arr = new List&amp;lt;int&amp;gt;(){ 4, 3, 2, 1 };
for (int i = 0; i &amp;lt; arr.Count; i++)
    Console.WriteLine(arr[i]) // 4 3 2 1&lt;/pre&gt;
  &lt;p id=&quot;SWx4&quot;&gt;Но как перебрать неиндексируемые коллекции, например Dictionary (о нем чуть ниже пойдет речь:)?!&lt;/p&gt;
  &lt;p id=&quot;cJth&quot;&gt;Все коллекции, которые были и будут рассмотрены в данной статье, относятся к специальному типу коллекций – перечислимым (Enumerable). Для перебора элементов перечислимых коллекций существует специальная логика, за которую отвечает специальный объект - Enumerator. Для перебора любой перечисляемой коллекции необходимо сделать следующее:&lt;/p&gt;
  &lt;pre id=&quot;aolA&quot; data-lang=&quot;clike&quot;&gt;List&amp;lt;int&amp;gt; someList = new List&amp;lt;int&amp;gt;() { 1, 2, 3, 4 };

List&amp;lt;int&amp;gt;.Enumerator it = someList.GetEnumerator(); // получаем Enumerator

while(it.MoveNext()) // Двигаемся по коллекции
{
    Console.WriteLine(it.Current); // Выводим значение
}&lt;/pre&gt;
  &lt;p id=&quot;aG4j&quot;&gt;1) Получаем объект &lt;code&gt;Enumerator&lt;/code&gt;, обращаясь к методу &lt;code&gt;GetEnumerator()&lt;/code&gt; у экземпляра коллекции и сохраняя его в переменную &lt;code&gt;it&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;R62w&quot;&gt;2) С помощью цикла &lt;code&gt;while&lt;/code&gt; обращаемся к методу &lt;code&gt;MoveNext()&lt;/code&gt; у полученного объекта, который двигает нас по коллекции.&lt;/p&gt;
  &lt;p id=&quot;51vs&quot;&gt;3) Свойство &lt;code&gt;Current&lt;/code&gt; возвращает текущее значение, на которое указывает Enumerator.&lt;/p&gt;
  &lt;p id=&quot;jJVi&quot;&gt;4) Метод &lt;code&gt;MoveNext()&lt;/code&gt; возвращается &lt;code&gt;false&lt;/code&gt;, когда элементы в коллекции заканчиваются.&lt;/p&gt;
  &lt;p id=&quot;qvoB&quot;&gt;Здесь важно запомнить, что &lt;code&gt;Enumerator&lt;/code&gt; создается для текущего состояния коллекции! Это значит, что если изменить количество элементов в коллекции во время её перебора, то объект &lt;code&gt;Enumerator&lt;/code&gt; перестанет существовать, что приведет к ошибке в программе.&lt;/p&gt;
  &lt;p id=&quot;0LyZ&quot;&gt;Но, как мы уже говорили в одной из предыдущих статей про управляющие конструкции, в C# помимо циклов &lt;code&gt;for&lt;/code&gt;, &lt;code&gt;while&lt;/code&gt; и &lt;code&gt;do while&lt;/code&gt; существует также еще &lt;code&gt;foreach&lt;/code&gt;. По сути &lt;code&gt;foreach&lt;/code&gt; является синтаксическим сахаром и разворачивается при компиляции ровно в ту конструкцию, что была показана в предыдущем примере, но при его использовании наш код будет гораздо компактнее и понятнее, что не может не радовать)&lt;/p&gt;
  &lt;pre id=&quot;sv8H&quot;&gt;foreach(ТипЭлементаКоллекции имяПеременной in имяПеребираемойКоллекции)
{
    тело цикла
    (по имени переменной сможем обращаться к элементам коллекции)
}&lt;/pre&gt;
  &lt;p id=&quot;XeKG&quot;&gt;Пример:&lt;/p&gt;
  &lt;pre id=&quot;XvSp&quot; data-lang=&quot;clike&quot;&gt;List&amp;lt;int&amp;gt; someList = new List&amp;lt;int&amp;gt;() { 1, 2, 3, 4 };

foreach(int i in someList)
{
    Console.WriteLine(i);
}
// Вывод построчно: 1 2 3 4&lt;/pre&gt;
  &lt;p id=&quot;EjGP&quot;&gt;В результате работы данного цикла происходит последовательный перебор всех элементов коллекции. Тип переменной должен совпадать с типом элементов перебираемой коллекции.&lt;/p&gt;
  &lt;p id=&quot;gTE6&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;y9Pp&quot;&gt;List&amp;lt;T&amp;gt;&lt;/h3&gt;
  &lt;p id=&quot;hdYH&quot;&gt;Пришло время поближе познакомиться с коллекциями, с помощью которых можно свернуть горы!&lt;/p&gt;
  &lt;p id=&quot;qaNQ&quot;&gt;Аналогично созданию массива, мы можем создавать списки – список элементов с динамически изменяемым размером. Хоть &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; и называется списком, но под капотом построен с использованием массива, в связи с чем по сути является динамическим массивом, а не структурой данных связанным списком:) &lt;em&gt;За связанные списки в C# отвечает коллекция &lt;code&gt;LinkedList&amp;lt;T&amp;gt;&lt;/code&gt;.&lt;/em&gt; &lt;/p&gt;
  &lt;pre id=&quot;dHOr&quot; data-lang=&quot;clike&quot;&gt;string[] s = { &amp;quot;hey&amp;quot;, &amp;quot;hi&amp;quot;, &amp;quot;bye&amp;quot; };

List&amp;lt;string&amp;gt; list1 = new List&amp;lt;string&amp;gt;() { &amp;quot;yo&amp;quot;, &amp;quot;c#&amp;quot; };

List&amp;lt;string&amp;gt; list2 = new List&amp;lt;string&amp;gt;(s); // содержит все элемнты массива s

List&amp;lt;string&amp;gt; list3 = new List&amp;lt;string&amp;gt;(list1); // содержит все элементы
                                              // листа list1&lt;/pre&gt;
  &lt;p id=&quot;2SvW&quot;&gt;Методы добавления элементов:&lt;/p&gt;
  &lt;ul id=&quot;63P2&quot;&gt;
    &lt;li id=&quot;CDmQ&quot;&gt;&lt;code&gt;Add()&lt;/code&gt; – добавляет переданный в качестве аргумента элемент в конец коллекции, тип передаваемого элемента должен совпадать с типом, которые хранит коллекция;&lt;/li&gt;
    &lt;li id=&quot;sFzq&quot;&gt;&lt;code&gt;AddRange() &lt;/code&gt;– добавляет переданный в качестве аргумента диапазон значений в конец коллекции, типы также должны совпадать.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;TGP5&quot; data-lang=&quot;clike&quot;&gt;List&amp;lt;string&amp;gt; list1 = new List&amp;lt;string&amp;gt; { &amp;quot;yo&amp;quot;, &amp;quot;c#&amp;quot; };
List&amp;lt;string&amp;gt; list2 = new List&amp;lt;string&amp;gt; { &amp;quot;oy&amp;quot; };

list2.Add(&amp;quot;#c&amp;quot;); // Добавляем элемент в конец второго списка

list1.AddRange(list2); // Добавляем все элементы второго
                       // списка в конец первого
list1.AddRange(list1); // Добавляем элементы первого списка
                       // в конец самого себя
foreach (string item in list1)
{
    Console.WriteLine(item);
}
// Вывод построчно: &amp;quot;yo&amp;quot; &amp;quot;c#&amp;quot; &amp;quot;oy&amp;quot; &amp;quot;#c&amp;quot; &amp;quot;yo&amp;quot; &amp;quot;c#&amp;quot; &amp;quot;oy&amp;quot; &amp;quot;#c&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;ZOms&quot;&gt;При расширении &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt;  изменение размера &lt;code&gt;Count &lt;/code&gt;и вместимости &lt;code&gt;Capacity &lt;/code&gt;происходит аналогично &lt;code&gt;ArrayList&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;1zzi&quot;&gt;Методы вставки элементов:&lt;/p&gt;
  &lt;ul id=&quot;0hQk&quot;&gt;
    &lt;li id=&quot;S8iG&quot;&gt;&lt;code&gt;Insert()&lt;/code&gt; – вставка элемента, в качестве аргументов принимает индекс, на место которого мы хотим произвести вставку, и вставляемый элемент;&lt;/li&gt;
    &lt;li id=&quot;pQcu&quot;&gt;&lt;code&gt;InsertRange()&lt;/code&gt; – вставка диапазона значений, в качестве аргументов принимает индекс, на место которого мы хотим произвести вставку, и вставляемую коллекцию.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;qCsX&quot; data-lang=&quot;clike&quot;&gt;List&amp;lt;string&amp;gt; list1 = new List&amp;lt;string&amp;gt; { &amp;quot;yo&amp;quot;, &amp;quot;c#&amp;quot; };
List&amp;lt;string&amp;gt; list2 = new List&amp;lt;string&amp;gt; { &amp;quot;oy&amp;quot; };

list1.Insert(0, &amp;quot;hello world&amp;quot;); // вставляем в список на место
                                // первого элемента
list1.Insert(list1.Count, &amp;quot;.NET&amp;quot;); // вставляем в конец списка

list2.InsertRange(list2.Count, list1); // вставляем все элементы первого
                                       // списка в конец второго
foreach (string item in list2)
{
    Console.WriteLine(item);
}
// Вывод построчно: &amp;quot;oy&amp;quot; &amp;quot;hello world&amp;quot; &amp;quot;yo&amp;quot; &amp;quot;c#&amp;quot; &amp;quot;.NET&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;7vSw&quot;&gt;Методы удаления элементов:&lt;/p&gt;
  &lt;ul id=&quot;fkPM&quot;&gt;
    &lt;li id=&quot;qYn8&quot;&gt;Remove() – удаляет первое вхождение элемента, переданного в качестве аргумента, возвращает true, если элемент был успешно удален, в противном случае – false (например, при передаче несуществующего в коллекции элемента);&lt;/li&gt;
    &lt;li id=&quot;K7GB&quot;&gt;RemoveAt() – удаляет элемент по индексу, в качестве аргумента принимает индекс, выход за границы массива как обычно приводит к ошибке;&lt;/li&gt;
    &lt;li id=&quot;7iOb&quot;&gt;RemoveRange() – удаляет все элементы коллекции в указанном диапазоне: с указанного индекса удаляется число указываемых элементов, выходить за границы коллекции всё еще нельзя.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;Hows&quot; data-lang=&quot;clike&quot;&gt;List&amp;lt;string&amp;gt; list1 = new List&amp;lt;string&amp;gt;{ &amp;quot;1&amp;quot;, &amp;quot;2&amp;quot;, &amp;quot;3&amp;quot;, &amp;quot;3&amp;quot;, &amp;quot;3&amp;quot;, &amp;quot;4&amp;quot;};
List&amp;lt;string&amp;gt; list2 = new List&amp;lt;string&amp;gt;{ &amp;quot;1&amp;quot;, &amp;quot;1&amp;quot;, &amp;quot;1&amp;quot;, &amp;quot;1&amp;quot;, &amp;quot;1&amp;quot;, &amp;quot;2&amp;quot;, &amp;quot;3&amp;quot;, &amp;quot;4&amp;quot; };

list1.Remove(&amp;quot;3&amp;quot;);

foreach (var item in list1)
{
    Console.WriteLine(item);
}
// Вывод построчно: &amp;quot;1&amp;quot; &amp;quot;2&amp;quot; &amp;quot;3&amp;quot; &amp;quot;3&amp;quot; &amp;quot;4&amp;quot;

list1.RemoveAt(2);

foreach (var item in list1)
{
    Console.WriteLine(item);
}
//Вывод построчно: &amp;quot;1&amp;quot; &amp;quot;2&amp;quot; &amp;quot;3&amp;quot; &amp;quot;4&amp;quot;

list2.RemoveRange(0, 4);

foreach (var item in list2)
{
    Console.WriteLine(item);
}
// Вывод построчно: &amp;quot;1&amp;quot; &amp;quot;2&amp;quot; &amp;quot;3&amp;quot; &amp;quot;4&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;kI8F&quot;&gt;Здесь стоит отметить, что операции вставки и удаления из линейных коллекций приводят к сдвигу всех элементов, что является не самым эффективным действием:&lt;/p&gt;
  &lt;figure id=&quot;QsJ0&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/1c/f9/1cf9f308-202f-40d3-8f26-5685f8b96128.png&quot; width=&quot;350&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;3PYC&quot;&gt;Мы можем использовать эти методы в ряде случаев, но при необходимости часто их использовать на больших объемах данных лучше прибегнуть к использованию коллекций, внутренняя реализация которых позволяет осуществлять эти операции эффективнее. О таких коллекциях мы также поговорим в будущем.&lt;/p&gt;
  &lt;p id=&quot;w8lW&quot;&gt;Методы поиска:&lt;/p&gt;
  &lt;ul id=&quot;PyxJ&quot;&gt;
    &lt;li id=&quot;U6kP&quot;&gt;Contains() – возвращает true или false в зависимости от того, существует ли в коллекции переданный в качестве аргумента элемент;&lt;/li&gt;
    &lt;li id=&quot;rE9b&quot;&gt;Find() – возвращает первый элемент, удовлетворяющий условию, подробнее к этому мы обязательно вернемся в последующих статьях.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;rQGF&quot;&gt;Прочие методы:&lt;/p&gt;
  &lt;ul id=&quot;IvvY&quot;&gt;
    &lt;li id=&quot;x6U9&quot;&gt;Reverse() – смена порядка элементов на обратный;&lt;/li&gt;
    &lt;li id=&quot;Imfl&quot;&gt;ToArray() – возвращает все элементы коллекции в виде массива соответствующего типа.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;6puy&quot;&gt;А также множество других методов, к которым мы будем обращаться по мере роста наших возможностей!&lt;/p&gt;
  &lt;p id=&quot;vJfJ&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;rspt&quot;&gt;Хэш-функции&lt;/h3&gt;
  &lt;p id=&quot;sUXZ&quot;&gt;Хэш-функциями называют некоторые функции (методы), которые осуществляют преобразование некоторых входных данных произвольной длины в выходную строку фиксированной длины. Результат такого преобразования называют хэшом, хэш-суммой или хэш-кодом. Существует множество различных методов хэширования, которые отличаются друг от друга производительностью и частотой получения коллизий (случаев, когда хэш-функция дает один хэш-код для различных входных данных).&lt;/p&gt;
  &lt;p id=&quot;W7vN&quot;&gt;Хэш-функции используются при:&lt;/p&gt;
  &lt;ul id=&quot;gJrR&quot;&gt;
    &lt;li id=&quot;OQnS&quot;&gt;построении ассоциативных массивов;&lt;/li&gt;
    &lt;li id=&quot;3M1d&quot;&gt;поиске дубликатов в коллекциях, хранящих значения без повторений;&lt;/li&gt;
    &lt;li id=&quot;ThuI&quot;&gt;сохранении паролей в системах защиты в виде хэш-кода;&lt;/li&gt;
    &lt;li id=&quot;wKr7&quot;&gt;при вычислении контрольных сумм от данных (сигнала) для последующего обнаружения в них ошибок, возникающих при хранении и/или передаче данных&lt;/li&gt;
    &lt;li id=&quot;oVtf&quot;&gt;и другое...&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;C6pU&quot;&gt;Хэш-таблица - это структура данных для хранения пар ключ-значение, где каждому значению соответствует ключ-хэшкод. Основным преимуществом хэш-таблиц является то, что доступ к их элементам осуществляется за константное время (об этом мы еще успеем поговорить позже в отдельном материале про хэш таблицы и оценку скорости работы алгоритмов:)&lt;/p&gt;
  &lt;h3 id=&quot;ulRp&quot;&gt;HashSet&lt;/h3&gt;
  &lt;p id=&quot;ZhAo&quot;&gt;HashSet – неиндексируемая коллекция множества, содержащая все значения в единственном числе, т.е. без повторений. Добавление уже существующих значений в эту коллекцию не даст никакого результата, добавление не будет совершено.&lt;/p&gt;
  &lt;pre id=&quot;dX4w&quot; data-lang=&quot;clike&quot;&gt;HashSet&amp;lt;int&amp;gt; firstSet = new HashSet&amp;lt;int&amp;gt; { 1, 2, 2, 3, 4, 4, 4, 5, 6, 6 };

foreach (var item in firstSet)
{
    Console.WriteLine(item);
}
// Вывод построчно: 1 2 3 4 5 6

List&amp;lt;int&amp;gt; someList = new List&amp;lt;int&amp;gt;{ 6, 6, 7, 7, 8, 9, 10, 10 };
HashSet&amp;lt;int&amp;gt; secondSet = new HashSet&amp;lt;int&amp;gt;(someList);

foreach (var item in secondSet)
{
    Console.WriteLine(item);   
}
// Вывод построчно: 6 7 8 9 10&lt;/pre&gt;
  &lt;p id=&quot;3Bmi&quot;&gt;&lt;em&gt;HashSet является ассоциативным контейнером (с каждым значением ассоциируется ключ) и выстраивается на основе хэш-таблицы, где каждому объекту будет соответствовать хэш-код данного объекта.&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;Oo0c&quot;&gt;Так как HashSet является по сути хэш-таблицей, то порядок добавления элементов во множество никак не влияет на порядок их хранения, они не хранятся в той последовательности, что их добавили. HashSet упорядочивает и хранит элементы по значению их хэша. Кроме того, HashSet это неиндексируемая коллекция, т.е. мы не можем обратиться к какому-то элементу коллекции с помощью квадратных скобок, как мы делали это с массивами и списками.&lt;/p&gt;
  &lt;p id=&quot;ObhH&quot;&gt;Добавлять новые элементы во множество можно с помощью метода &lt;code&gt;Add()&lt;/code&gt;, при добавлении уже существующих элементов ничего не произойдет:&lt;/p&gt;
  &lt;pre id=&quot;QZdP&quot; data-lang=&quot;clike&quot;&gt;HashSet&amp;lt;int&amp;gt; someHashSet = new HashSet&amp;lt;int&amp;gt;(); // создали пустой HashSet

someHashSet.Add(1);
someHashSet.Add(1);

foreach(var item in someHashSet)
{
    Console.WriteLine(item);
}
// Вывод: 1&lt;/pre&gt;
  &lt;p id=&quot;wEqb&quot;&gt;Удаление происходит аналогично списку с помощью метода Remove() и передачи значения элемента, который мы хотим удалить:&lt;/p&gt;
  &lt;pre id=&quot;6Imn&quot; data-lang=&quot;clike&quot;&gt;HashSet&amp;lt;int&amp;gt; someHashSet = new HashSet&amp;lt;int&amp;gt;() { 1, 2, 3, 4};

someHashSet.Remove(2);
someHashSet.Remove(10);

foreach(var item in someHashSet)
{
    Console.WriteLine(item);
}
// Вывод построчно: 1 3 4&lt;/pre&gt;
  &lt;p id=&quot;OgUu&quot;&gt;Метод &lt;code&gt;Contains()&lt;/code&gt; позволяет узнать существует ли переданный элемент в коллекции, в качестве аргумента принимает интересующее значение, а возвращает значение типа &lt;code&gt;bool&lt;/code&gt; в соответствии с результатом работы:&lt;/p&gt;
  &lt;pre id=&quot;1RsL&quot; data-lang=&quot;clike&quot;&gt;HashSet&amp;lt;int&amp;gt; someHashSet = new HashSet&amp;lt;int&amp;gt;() { 1, 2, 3, 4};

if(someHashSet.Contains(1))
{
    Console.WriteLine(&amp;quot;1 is in HashSet&amp;quot;); // Вывод: 1 is in HashSet
}

if(someHashSet.Contains(10))
{
    Console.WriteLine(&amp;quot;10 is in HashSet&amp;quot;); // Сюда мы не зайдем т.к. 10 нет
}                                          // в нашем множестве&lt;/pre&gt;
  &lt;p id=&quot;Wxd2&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Ltho&quot;&gt;Dictionary&lt;/h3&gt;
  &lt;p id=&quot;HnWN&quot;&gt;Dictionary (также именуемый словарём или мапой) – ассоциативный массив, позволяющий хранить элементы в виде пар &amp;quot;ключ-значение&amp;quot;, например:&lt;/p&gt;
  &lt;pre id=&quot;F9LO&quot;&gt;1 - &amp;quot;Котик&amp;quot;
2 - &amp;quot;Собачка&amp;quot;
3 - &amp;quot;Птичка&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;LWip&quot;&gt;&lt;em&gt;Dictionary также как и HashSet выстроен на хэш-таблице, однако вместо того, чтобы применять хэш-функцию к значения и обращаться к объектом по нему, в Dictionary для этого имеется дополнительный элемент - ключ.&lt;/em&gt;&lt;/p&gt;
  &lt;pre id=&quot;vzPJ&quot; data-lang=&quot;clike&quot;&gt;Dictionary&amp;lt;int, string&amp;gt; someDict = new Dictionary&amp;lt;int, string&amp;gt;();&lt;/pre&gt;
  &lt;p id=&quot;ZENK&quot;&gt;С помощью кода выше мы инициализируем пустой словарь, элементы которого состоят из пар целочисленного ключа и строкового значения. Инициализатор для словаря работает следующим образом:&lt;/p&gt;
  &lt;pre id=&quot;hIRU&quot; data-lang=&quot;clike&quot;&gt;Dictionary&amp;lt;int, string&amp;gt; someDict = new Dictionary&amp;lt;int, string&amp;gt;()
{
    { 1, &amp;quot;Kotik&amp;quot; },
    { 2, &amp;quot;Soba4ka&amp;quot; },
    { 3, &amp;quot;Pti4ka&amp;quot; }
};&lt;/pre&gt;
  &lt;p id=&quot;E8Ul&quot;&gt;Все элементы словаря хранятся в виде структур (о структурах обязательно поговорим позже) KeyValuePair, где от ключа берется хеш для доступа к значениям, т.е. порядок добавления не влияет на порядок хранения элементов. Ключ не может быть &lt;code&gt;null&lt;/code&gt;. В качестве ключа может быть использован любой класс или структура, для которых может быть посчитан хэш-код.&lt;/p&gt;
  &lt;p id=&quot;2xCb&quot;&gt;Ключи для всех элементов словаря уникальны, попытка добавления элемента, по ключу которого уже существует пара, приведет к ошибке:&lt;/p&gt;
  &lt;pre id=&quot;dk2d&quot; data-lang=&quot;clike&quot;&gt;Dictionary&amp;lt;int, string&amp;gt; someDict = new Dictionary&amp;lt;int, string&amp;gt;();

someDict.Add(1, &amp;quot;Vanya&amp;quot;); // Добавили пару 1 - &amp;quot;Vanya&amp;quot;
someDict.Add(2, &amp;quot;Sanya&amp;quot;); // Добавили пару 2 - &amp;quot;Sanya&amp;quot;
someDict.Add(1, &amp;quot;Oleg&amp;quot;); // Олег все испортил - ошибка, по ключу 1 уже
                         // существует элемент&lt;/pre&gt;
  &lt;p id=&quot;0tph&quot;&gt;Для безопасного удаления необходимо осуществлять проверку существования ключа с помощью метода &lt;code&gt;ContainsKey&lt;/code&gt;, принимающего в качестве аргумента ключ, существование которого мы хотим проверить, и возвращающий значение &lt;code&gt;bool&lt;/code&gt; в соответствии с результатом:&lt;/p&gt;
  &lt;pre id=&quot;xJaw&quot; data-lang=&quot;clike&quot;&gt;Dictionary&amp;lt;int, string&amp;gt; someDict = new Dictionary&amp;lt;int, string&amp;gt;();

someDict.Add(1, &amp;quot;Vanya&amp;quot;); // Добавили пару 1 - &amp;quot;Vanya&amp;quot;
someDict.Add(2, &amp;quot;Sanya&amp;quot;); // Добавили пару 2 - &amp;quot;Sanya&amp;quot;

if (!someDict.ContainsKey(1))
{
    someDict.Add(1, &amp;quot;Oleg&amp;quot;); // Добавляем Олега только если нет элемента
}                            // с ключом 1&lt;/pre&gt;
  &lt;p id=&quot;5M5q&quot;&gt;Или можно использовать специальный безопасный метод &lt;code&gt;TryAdd()&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;6xHV&quot; data-lang=&quot;clike&quot;&gt;Dictionary&amp;lt;int, string&amp;gt; someDict = new Dictionary&amp;lt;int, string&amp;gt;();

someDict.TryAdd(1, &amp;quot;Vanya&amp;quot;); // Добавили пару 1 - &amp;quot;Vanya&amp;quot;
someDict.TryAdd(2, &amp;quot;Sanya&amp;quot;); // Добавили пару 2 - &amp;quot;Sanya&amp;quot;
someDict.TryAdd(1, &amp;quot;Oleg&amp;quot;); // Мы остановили Олега, ничего не произойдет,
                            // элемент добавлен не будет&lt;/pre&gt;
  &lt;p id=&quot;sB3L&quot;&gt;Удаление элемента из коллекции также происходит по ключу:&lt;/p&gt;
  &lt;pre id=&quot;vN5x&quot; data-lang=&quot;clike&quot;&gt;Dictionary&amp;lt;int, string&amp;gt; someDict = new Dictionary&amp;lt;int, string&amp;gt;()
{
    { 1, &amp;quot;Kotik&amp;quot; },
    { 2, &amp;quot;Soba4ka&amp;quot; },
    { 3, &amp;quot;Pti4ka&amp;quot; }
};

someDict.Remove(3); // Удалили пару 3 - &amp;quot;Pti4ka&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;JYi1&quot;&gt;Для перебора элементов Dictionary также удобно использовать foreach:&lt;/p&gt;
  &lt;pre id=&quot;FHtj&quot; data-lang=&quot;clike&quot;&gt;Dictionary&amp;lt;int, string&amp;gt; someDict = new Dictionary&amp;lt;int, string&amp;gt;()
{
    { 1, &amp;quot;Kotik&amp;quot; },
    { 2, &amp;quot;Soba4ka&amp;quot; },
    { 3, &amp;quot;Pti4ka&amp;quot; }
};

foreach (KeyValuePair&amp;lt;int, string&amp;gt; item in someDict)
{
    Console.WriteLine($&amp;quot;{item.Key} - {item.Value}&amp;quot;);
}
// Вывод:
// 1 - Kotik
// 2 - Soba4ka
// 3 - Pti4ka&lt;/pre&gt;
  &lt;p id=&quot;IfWv&quot;&gt;С помощью квадратных скобок мы можем работать со значениями, находящимися в паре с соответствующим ключом:&lt;/p&gt;
  &lt;pre id=&quot;Ii97&quot; data-lang=&quot;clike&quot;&gt;Console.WriteLine(someDict[&amp;quot;first&amp;quot;]); // Вывод: Kotik
Console.WriteLine(someDict[&amp;quot;second&amp;quot;]); // Вывод: Soba4ka
Console.WriteLine(someDict[&amp;quot;third&amp;quot;]); // Вывод: Pti4ka&lt;/pre&gt;
  &lt;p id=&quot;k9nj&quot;&gt;При этом обращение к несуществующему ключу приведет к ошибке:&lt;/p&gt;
  &lt;pre id=&quot;p3Og&quot; data-lang=&quot;clike&quot;&gt;Console.WriteLine(someDict[&amp;quot;fourth&amp;quot;]); // ОШИБКА!&lt;/pre&gt;
  &lt;p id=&quot;hvJh&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;4W9t&quot;&gt;Пока по структурам данных все, но мы обязательно вернемся к этой теме еще не раз, так как еще оооочень много чего следует дорасказать, и это была лишь ознакомительная статья о том, с чем предстоит в дальнейшем постоянно сталкиваться) Далее на канале выйдет небольшой пост о синтаксическом сахаре &lt;code&gt;var&lt;/code&gt; и не большая статья о передаче объектов по значению и по ссылке, и будем начинать выкладывать различные задачки для практики и отработки материала. Так что подписывайтесь на &lt;a href=&quot;https://t.me/serious_seesharp&quot; target=&quot;_blank&quot;&gt;тг-канал&lt;/a&gt; ставьте лайки и вот этого всего можно побольше!)&lt;/p&gt;

</content></entry><entry><id>notserious_seesharp:classes_objects</id><link rel="alternate" type="text/html" href="https://teletype.in/@notserious_seesharp/classes_objects?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=notserious_seesharp"></link><title>Классы и объекты</title><published>2022-10-25T12:39:11.940Z</published><updated>2024-01-05T19:05:22.233Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/76/53/765355c5-f9dd-4476-a183-e964de944e1b.png"></media:thumbnail><category term="osnovy-c" label="Основы C#"></category><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/d1/f0/d1f09196-2ba4-4590-ba47-ea237babd48d.png&quot;&gt;Привет! В данной статье мы продолжим разбираться в особенностях устройства основных механизмов классов в C#, немного пофилософствуем о жизненном цикле объектов и сборщике мусора, а также узнаем о типах перечислений.</summary><content type="html">
  &lt;p id=&quot;alcT&quot;&gt;Привет! В данной статье мы продолжим разбираться в особенностях устройства основных механизмов классов в C#, немного пофилософствуем о жизненном цикле объектов и сборщике мусора, а также узнаем о типах перечислений.&lt;/p&gt;
  &lt;figure id=&quot;JZgA&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d1/f0/d1f09196-2ba4-4590-ba47-ea237babd48d.png&quot; width=&quot;1200&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;nNni&quot;&gt;Шаблоны проектирования&lt;/h3&gt;
  &lt;p id=&quot;XWRd&quot;&gt;В программировании существует множество типовых проблем, подходы решения к которым были придуманы нашими коллегами. Способы решения таких проблем называются шаблонами проектирования или паттернами (от англ. pattern – шаблон). По мере овладения синтаксисом языка и прокачки своего скилла вы все чаще будете встречаться с необходимостью решать типовые задачи и писать читаемый и поддерживаемый код, решение данных задач может быть осуществлено с помощью реализации существующих общепринятых в сообществе подходов. Такое решение может быть эффективно, а код удобен в дальнейшей поддержке и использовании.&lt;/p&gt;
  &lt;p id=&quot;jpm3&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;IEjy&quot;&gt;Методы доступа к данным класса&lt;/h3&gt;
  &lt;p id=&quot;N5vG&quot;&gt;Одним из общепринятых подходов организации механизмов чтения и записи полей является реализация методов доступа, часто именуемых аксессорами (от access – доступ):&lt;/p&gt;
  &lt;ul id=&quot;wmnd&quot;&gt;
    &lt;li id=&quot;bNUC&quot;&gt;&lt;code&gt;getter&lt;/code&gt; (get – получать) – общее именование методов доступа, позволяющих получать данные;&lt;/li&gt;
    &lt;li id=&quot;8qzW&quot;&gt;&lt;code&gt;setter&lt;/code&gt; (set – задавать) – общее именование методов доступа, позволяющих задавать данные.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;Zu85&quot;&gt;Простыми словами: идея данного подхода заключается в том, что при сокрытии от посторонних глаз полей модификатором private мы вручную определяем поведение при работе с ними через методы, сохраняя таким образом четкий контроль состояния полей объекта. Более сложно: это предпочтительный метод в объектно-ориентированном программировании, позволяющий достичь необходимый уровень абстракции и инкапсуляции, более подробно об этом мы обязательно поговорим в дальнейших статьях.&lt;/p&gt;
  &lt;pre id=&quot;d8M5&quot; data-lang=&quot;clike&quot;&gt;class Human 
{ 
    private string name;
    private int age; 
    
    public string GetName() 
    { 
        return name; 
    } 
    
    public void SetName(string nameToSet) 
    { 
        name = nameToSet; 
    } 
    
    public int GetAge() 
    { 
        return age; 
    } 
    
    public void SetAge(int ageToSet) 
    { 
        age = ageToSet; 
    } 
}&lt;/pre&gt;
  &lt;p id=&quot;iFob&quot;&gt;В представленном примере у нас есть класс человечка, хранящий имя и возраст в виде приватных полей. Для организации доступа к данным полям создаем методы доступа, позволяющие получать и задавать значения полей. Пример совсем простой, но нужно понять, что такой подход позволит нам задать дополнительную логику обработке входных данных, обеспечивая контроль доступа.&lt;/p&gt;
  &lt;p id=&quot;Z9XF&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;hIJa&quot;&gt;Свойства классов&lt;/h3&gt;
  &lt;p id=&quot;Je06&quot;&gt;Одним из множества приятных плюсов C# является наличие различного синтаксического сахара. &lt;em&gt;Синтаксический сахар - синтаксические возможности языка, которые дублируют в себе функционал уже имеющихся синтаксических конструкций, но являются более короткими аналогами и преобразуются на этапе компиляции в эти же самые конструкции, позволяя тем самым более быстро писать понятный код.&lt;/em&gt;&lt;/p&gt;
  &lt;figure id=&quot;KMgd&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/ba/65/ba651412-6240-42a4-925d-bfbfffa3e2bc.jpeg&quot; width=&quot;600&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;0wue&quot;&gt;В предыдущей &lt;a href=&quot;https://teletype.in/@notserious_seesharp/control_statements&quot; target=&quot;_blank&quot;&gt;статье&lt;/a&gt; мы познакомились с основными членами класса: полями и методами. Свойство класса — это специальный член класса, предоставляющий гибкий механизм для чтения, записи и вычислений значения поля, сочетая в себе одновременно возможности поля класса и методов доступа к ним. Свойства являются синтаксическим сахаром.&lt;/p&gt;
  &lt;p id=&quot;4onY&quot;&gt;Общий шаблон объявления свойств выглядит следующим образом:&lt;/p&gt;
  &lt;pre id=&quot;QkaH&quot;&gt;[модификатор доступа] [тип] ИмяСвойства
{
    get { инструкции, выполняемые для возвращения значения }
    set { инструкции, выполняемые для установки значения }
}&lt;/pre&gt;
  &lt;p id=&quot;exqx&quot;&gt;Разберемся на примере:&lt;/p&gt;
  &lt;pre id=&quot;yAYb&quot; data-lang=&quot;clike&quot;&gt;class Human 
{ 
    private string name; 
    private int age; 
    
    public string Name 
    { 
        get { return name; } 
        set { name = value; } 
    } 
    
    public int Age 
    { 
        get { return age; }  
        set { age = value; } 
    }
}&lt;/pre&gt;
  &lt;p id=&quot;f81w&quot;&gt;Сравните получившуюся конструкцию с предыдущем решением. Выглядит гораздо компактнее, не правда ли? Для наших полей имени и возраста мы создали свойства, именуемые согласно кодстайлу с большой буквы. Для свойств определяем модификатор доступа, тип. Внутри с помощью аксессоров &lt;code&gt;get&lt;/code&gt; и &lt;code&gt;set&lt;/code&gt; определяем логику получения и задавания данных:&lt;/p&gt;
  &lt;ul id=&quot;FH79&quot;&gt;
    &lt;li id=&quot;g0vl&quot;&gt;в блоке &lt;code&gt;get&lt;/code&gt; определяем действия для получения значения свойства, с помощью оператора return возвращаем какое-то значение (тип возвращаемого значения должен совпадать с типом свойства);&lt;/li&gt;
    &lt;li id=&quot;Hsq5&quot;&gt;в блоке &lt;code&gt;set&lt;/code&gt; устанавливаем значение свойства, с помощью параметра value обозначается переданное свойству значение (т.е. данным ключевым словом обозначается параметр, значение которого передается при присваивании свойству).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;ILVd&quot;&gt;Наше свойство является посредником между нашими скрытыми полями и внешним кодом.&lt;/p&gt;
  &lt;p id=&quot;wmYa&quot;&gt;Обращение со свойствами класса осуществляется с тем же синтаксисом, что и с обычными полями класса:&lt;/p&gt;
  &lt;pre id=&quot;Ys96&quot; data-lang=&quot;clike&quot;&gt;Human human = new Human(); 
human.Name = &amp;quot;Vasya&amp;quot;;    // присваиваем значение свойству 
human.Age = 23;          // и здесь

Console.WriteLine(human.Name); // Vasya 
Console.WriteLine(human.Age);  // 23&lt;/pre&gt;
  &lt;p id=&quot;RS02&quot;&gt;Присваивание значения свойству приводит к вызову аксессора &lt;code&gt;set&lt;/code&gt;, значение передается через &lt;code&gt;value&lt;/code&gt;. Возвращение свойства приводит к вызову аксессора &lt;code&gt;get&lt;/code&gt; и возврату значения с помощью &lt;code&gt;return&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;s4zN&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;xxt1&quot;&gt;Автоматические свойства&lt;/h3&gt;
  &lt;p id=&quot;ypVl&quot;&gt;И здесь наш любимый C# подкидывает нам еще больше синтаксического сахара. В случае, когда в методах доступа не требуется определение дополнительной логики (как в нашем примере), мы можем прибегнуть к использованию автоматически реализуемых свойств или просто авто свойств.&lt;/p&gt;
  &lt;pre id=&quot;9wvm&quot; data-lang=&quot;clike&quot;&gt;class Human 
{ 
    public string Name { get; set; } 
    public int Age { get; set; } 
}&lt;/pre&gt;
  &lt;p id=&quot;8dgL&quot;&gt;При использовании таких свойств не нужно прописывать поля, с которыми будет работать свойства и описывать базовую логику присваивания и возврата в теле &lt;code&gt;set&lt;/code&gt; и &lt;code&gt;get&lt;/code&gt;. Компилятор на этапе компиляции создаст из свойства соответствующие приватные поля и методы для обращения к этим полям, а наш код маленьким и приятным для глаза)&lt;/p&gt;
  &lt;p id=&quot;Gxis&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;lqyO&quot;&gt;Конструкторы классов&lt;/h3&gt;
  &lt;p id=&quot;zEUc&quot;&gt;В статье &lt;a href=&quot;https://teletype.in/@notserious_seesharp/classes_basic&quot; target=&quot;_blank&quot;&gt;&amp;quot;Классы, поля и методы&amp;quot;&lt;/a&gt;, обсуждая способ создания классов и работу оператора &lt;code&gt;new&lt;/code&gt;, было отмечено, что завершающим действием данного оператора является вызов конструктора экземпляра. Так что же такое конструктор?&lt;/p&gt;
  &lt;p id=&quot;z85r&quot;&gt;Конструктор – это специальный метод, вызываемый при создании объекта класса. Он используется для инициализации полей некоторыми значениями, выполнения какой-либо логики при создании объекта. Конструктор позволяет создавать 100% пригодный экземпляр класса, чтобы мы не имели чемодан без ручки. Конструктор имеет название, совпадающее с именем класса, и не имеет типа:&lt;/p&gt;
  &lt;pre id=&quot;42c5&quot;&gt;[модификатор доступа] ИмяКласса 
{ 
    необходимые инструкции 
}&lt;/pre&gt;
  &lt;p id=&quot;AEAB&quot;&gt;Существует два типа конструкторов: конструктор по умолчанию и параметризованный конструктор.&lt;/p&gt;
  &lt;p id=&quot;WnXX&quot;&gt;Под конструктором по умолчанию понимают конструктор без параметров, инициализирующий все поля класса значениями по умолчанию. В случае, когда мы явно не создаем никакого конструктора в коде, компилятор генерирует конструктор по умолчанию за нас:&lt;/p&gt;
  &lt;pre id=&quot;xSm1&quot; data-lang=&quot;clike&quot;&gt;class Human
{
    public Human()
    {
    
    }
}&lt;/pre&gt;
  &lt;p id=&quot;vW7F&quot;&gt;Конструктор по умолчанию не генерируется в случае объявления собственных конструкторов.&lt;/p&gt;
  &lt;p id=&quot;1agF&quot;&gt;Под параметризованным конструктором понимают конструктор, имеющий как минимум один параметр:&lt;/p&gt;
  &lt;pre id=&quot;tX7Q&quot; data-lang=&quot;clike&quot;&gt;class Human
{
    public Human(string name, int age)
    {
    
    }
}&lt;/pre&gt;
  &lt;p id=&quot;7Rig&quot;&gt;Класс может иметь неограниченное число конструкторов, но стоит помнить, что при их объявлении важна последовательность типов параметров (но НЕ их имена), такая последовательность обязана быть уникальна, иначе мы получим ошибку:&lt;/p&gt;
  &lt;pre id=&quot;EKUm&quot; data-lang=&quot;clike&quot;&gt;class Human 
{ 
    public Human() // корректный конструктор 
    { 
    } 
    
    public Human(string name) // корректный конструктор 
    { 
    } 
    
    public Human(int age, string name) // корректный конструктор 
    { 
    } 
    
    public Human(string name, int age) // корректный конструктор 
    { 
    } 
    
    public Human(int age)    // Ошибка! 
    {                        // Порядок типов параметров данных 
    }                        // конструкторов совпадает
    public Human(int height) // Это два одинаковых конструктора! 
    { 
    } 
}&lt;/pre&gt;
  &lt;p id=&quot;p75k&quot;&gt;Инструкции, приведенные внутри тела конструктора, могут выполнять различные задачи: инициализация и проверка валидности полей, вывод вспомогательных сообщений и многое другое, но выполняться они будут единожды для каждого экземпляра класса – в момент его создания.&lt;/p&gt;
  &lt;pre id=&quot;TZaq&quot; data-lang=&quot;clike&quot;&gt;class Human 
{ 
    public string Name { get; set; } 
    public int Age { get; set; } 
    
    public Human() 
    { 
        Name = &amp;quot;Default guy&amp;quot;; 
        Age = 20; 
        Console.WriteLine(&amp;quot;Default guy is created&amp;quot;); 
    } 
    
    public Human(string name, int age) 
    { 
        Name = name; 
        Age = age; 
        Console.WriteLine(&amp;quot;Human is created&amp;quot;); 
    } 
}&lt;/pre&gt;
  &lt;pre data-lang=&quot;clike&quot; id=&quot;4E4U&quot;&gt;Human defaultHuman = new Human(); // вывод: &amp;quot;Default guy is created&amp;quot;
Console.WriteLine(defaultHuman.Name); // вывод: &amp;quot;Default guy&amp;quot;
Console.WriteLine(defaultHuman.Age); // вывод: 20

Human human = new Human(&amp;quot;Vasya&amp;quot;, 50); // вывод: &amp;quot;Human is created&amp;quot;
Console.WriteLine(human.Name); // вывод: &amp;quot;Vasya&amp;quot;
Console.WriteLine(human.Age); // вывод: 50&lt;/pre&gt;
  &lt;p id=&quot;npJn&quot;&gt;При создании новых экземпляров класса Human с помощью оператора &lt;code&gt;new&lt;/code&gt;, мы используем конструкторы, инициализируя поля объекта и выводя вспомогательное сообщение, оповещающее о создании человека.&lt;/p&gt;
  &lt;h3 id=&quot;lcNQ&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;4Bd4&quot;&gt;Жизненный цикл объекта, сборка мусора и деструктор&lt;/h3&gt;
  &lt;p id=&quot;HMpU&quot;&gt;Процесс создания объекта называют инстанцированием (instance – экземпляр). После создания, объект располагается где-то в куче, в оперативной памяти. В общем виде принято считать, что объект жив, пока хотя бы какая-нибудь переменная хранит адрес этого объекта, ссылается на него. Т.е. пока мы имеем возможность взаимодействовать с объектом – объект жив. Как только теряются все ссылки на объект, он становится недостижим для нас. Этот объект попрежнему существует, занимает какое-то место в памяти, но он бесполезен. Проще говоря данный объект является мусором, он лишь занимает наше драгоценное место в памяти. Именно поэтому все объекты, затерянные в виртуальной машине, помечаются как мусор.&lt;/p&gt;
  &lt;pre data-lang=&quot;clike&quot; id=&quot;69qc&quot;&gt;Human human1 = new Human(&amp;quot;Oleg&amp;quot;);
human1 = null;

Human human2 = new Human(&amp;quot;Vanya&amp;quot;);

Human human3 = new Human(&amp;quot;Sanya&amp;quot;);
human3 = human2;&lt;/pre&gt;
  &lt;p id=&quot;trAi&quot;&gt;Рассмотрим поподробнее пример выше. Мы создали 3 объекта класса человека, получили трех приличных членов общества: Олега, Ваню и Саню. Ссылку на Олега хранит лишь одна переменная – &lt;code&gt;human1&lt;/code&gt;. Сразу после инстанцирования Олега, мы занулили ссылку, указывающую на него, отрезав таким образом для себя связь с ним. Press F Олегу, нам больше до него не добраться:( Ссылок, указывающих на него больше нет, именно поэтому бедолага будет помечен как мусор. Затем после создания объектов Ваня и Саня, мы присваиваем ссылке &lt;code&gt;human3&lt;/code&gt; значение ссылки &lt;code&gt;human2&lt;/code&gt;. Таким образом, мы получаем две ссылки (&lt;code&gt;human2&lt;/code&gt; и &lt;code&gt;human3&lt;/code&gt;), ссылающиеся на один объект – Ваню. На Саню же теперь не указывает ни одна ссылка, он тоже становится бесполезен и помечается как мусор.&lt;/p&gt;
  &lt;figure id=&quot;zk37&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/76/97/76975b8f-8806-433a-96c6-fb07ed4f4b5e.png&quot; width=&quot;246&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;aKaf&quot;&gt;Все бесполезные объекты, помеченные как мусор, рано или поздно удаляются. Здесь в дело вступает сборщик мусора или &lt;code&gt;garbage collector&lt;/code&gt;. Сборщик мусора – специальный механизм, который отвечает за процесс управления памятью виртуальной машины CLR. Периодически запускаемый процесс сборки мусора удаляет бесполезные неиспользуемые объекты, освобождая таким образом место для новых. Сборщик мусора запускается НЕ сразу после потери всех ссылок на объект, такие мусорные объекты будут продолжать занимать память, а CLR сама определит когда их необходимо удалить, например, когда будет подходить к концу свободное место.&lt;/p&gt;
  &lt;p id=&quot;8oCu&quot;&gt;Доходя до бесполезных объектов, сборщик мусора вызывает у них специальный метод, который отвечает за уничтожение этого экземпляра класса. Такой специальный метод называется деструктор. Если конструктор вызывается при создании экземпляра класса, то деструктор вызывается при его уничтожении, все предельно логично. Каждый класс может иметь только 1 деструктор, вызывать его напрямую невозможно, он запускается автоматически. Деструктор аналогично конструктору имеет реализацию по умолчанию, которую при желании мы можем модифицировать. Для этого необходимо воспользоваться данной конструкцией:&lt;/p&gt;
  &lt;pre id=&quot;w03u&quot;&gt;~ИмяКласса() 
{ 
    тело деструктора 
}&lt;/pre&gt;
  &lt;p id=&quot;Nzm1&quot;&gt;Так, например, экземпляры нашего класса Human, могут прощаться с нами при уничтожении:&lt;/p&gt;
  &lt;pre data-lang=&quot;clike&quot; id=&quot;88sw&quot;&gt;~Human()
{
    Console.WriteLine(&amp;quot;Bye bye&amp;quot;);
}&lt;/pre&gt;
  &lt;p id=&quot;7w6n&quot;&gt;Если в теле деструктора указаны какие-либо инструкции, препятствующие корректному уничтожению объекта, то объект все равно будет удален с помощью вызова базовой реализации деструктора, а наши инструкции проигнорируются.&lt;/p&gt;
  &lt;p id=&quot;2PFy&quot;&gt;При завершении программы происходит полное высвобождение ресурсов, и все наши объекты удаляются.&lt;/p&gt;
  &lt;p id=&quot;64tr&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;wzev&quot;&gt;Статические классы&lt;/h3&gt;
  &lt;p id=&quot;26Xd&quot;&gt;Что делать в случаях, когда нам вообще не нужен экземпляр класса, чтобы реализовать необходимый функционал? Для решения подобных задач существуют статические классы. Создание таких классов происходит точно так же, как и обычных, только с добавлением ключевого слова &lt;code&gt;static&lt;/code&gt;:&lt;/p&gt;
  &lt;pre data-lang=&quot;clike&quot; id=&quot;AX98&quot;&gt;[Модификатор доступа] static class Имя_класса
{
    тело класса
}&lt;/pre&gt;
  &lt;p id=&quot;I2uS&quot;&gt;Мы уже неоднократно показывали статические классы в нашем коде – класс &lt;code&gt;Console&lt;/code&gt; является статическим. Статический класс позволяет удобно выносить общий функционал, который может быть использован в любой части программы.&lt;/p&gt;
  &lt;p id=&quot;BoP0&quot;&gt;Особенности статического класса:&lt;/p&gt;
  &lt;ul id=&quot;nNyb&quot;&gt;
    &lt;li id=&quot;iRba&quot;&gt;статические класс содержит только статические элементы;&lt;/li&gt;
    &lt;li id=&quot;tazT&quot;&gt;статический класс не может содержать обычные конструкторы класса;&lt;/li&gt;
    &lt;li id=&quot;CZ8V&quot;&gt;экземпляр статического класса невозможно создать;&lt;/li&gt;
    &lt;li id=&quot;QOR7&quot;&gt;статический класс не может быть родительским (более подробно об этом мы обязательно поговорим в следующих статьях).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;ceY1&quot;&gt;Обращение к элементам статического класса происходит через имя класса и оператор точки:&lt;/p&gt;
  &lt;pre id=&quot;5JAU&quot; data-lang=&quot;clike&quot;&gt;Console.WriteLine();&lt;/pre&gt;
  &lt;p id=&quot;7pgm&quot;&gt;В данном примере мы обратились к статическому классу &lt;code&gt;Console&lt;/code&gt; и вызвали статический метод &lt;code&gt;WriteLine()&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;hP5Z&quot;&gt;В общем случае мы не можем сказать, когда именно тот или иной класс будет загружен в память, но гарантировано, что класс будет загружен к моменту первого обращения к нему в программе, при этом будут загружены и его статические члены.&lt;/p&gt;
  &lt;p id=&quot;va5B&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;aTsf&quot;&gt;Статические конструкторы&lt;/h3&gt;
  &lt;p id=&quot;JqMM&quot;&gt;Но что если мы хотим проинициализировать статические члены любого класса или единожды выполнить какие-то действия до момента создания первого экземпляра нестатического класса? Для решения этой проблемы существуют статические конструкторы. Статический конструктор используется для инициализации любых статических элементов или для единовременного выполнения какого-либо действия. Статический конструктор вызывается автоматически при первом обращении к статическим членам класса или перед созданием первого экземпляра класса.&lt;/p&gt;
  &lt;pre id=&quot;SglB&quot;&gt;static ИмяКласса() // статический конструктор
{

}&lt;/pre&gt;
  &lt;p id=&quot;WWoT&quot;&gt;Для каждого класса неявно по умолчанию генерируется статический конструктор. Определяя собственный конструктор, мы на самом деле модифицируем дефолтный, добавляя в него дополнительные инструкции.&lt;/p&gt;
  &lt;p id=&quot;7AHu&quot;&gt;Важные замечания:&lt;/p&gt;
  &lt;ul id=&quot;k8Km&quot;&gt;
    &lt;li id=&quot;z4ro&quot;&gt;статический конструктор не имеет модификаторов доступа и не принимает аргументы;&lt;/li&gt;
    &lt;li id=&quot;aYaP&quot;&gt;статический конструктор нельзя вызывать напрямую, он вызывается автоматически при первом обращении к статическим членам класса или перед созданием первого экземпляра класса;&lt;/li&gt;
    &lt;li id=&quot;ZmaF&quot;&gt;статический конструктор отрабатывает единожды для класса;&lt;/li&gt;
    &lt;li id=&quot;biPo&quot;&gt;статический конструктор обязан быть один.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;vAXY&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;MCNJ&quot;&gt;Типы перечислений&lt;/h3&gt;
  &lt;p id=&quot;iCyK&quot;&gt;Тип перечисления – это тип значений, который определяется набором именованных констант целочисленного типа. Объявление перечисления происходит с помощью ключевого слова &lt;code&gt;enum&lt;/code&gt; и перечня значений:&lt;/p&gt;
  &lt;pre id=&quot;KCdj&quot;&gt;[Модификатор доступа] enum ИмяПеречисления
{
    значение1,
    значение2,
    значение3,
    ...
    значениеN
}&lt;/pre&gt;
  &lt;p id=&quot;9yrM&quot;&gt;Данный тип может быть очень удобен в случаях, когда мы имеем дело с заведомо известным числом состояний объекта. Так, например, мы можем представить дни недели в качестве типа перечислений:&lt;/p&gt;
  &lt;pre data-lang=&quot;clike&quot; id=&quot;RS9F&quot;&gt;public enum Day
{
     Monday,
     Tuesday,
     Wednesday,
     Thursday,
     Friday,
     Saturday,
     Sunday
}&lt;/pre&gt;
  &lt;p id=&quot;CNyc&quot;&gt;Мы определили свой первый тип перечислений, состоящий из 7 значений – 7 дней недели. Обращение к значениям перечисления осуществляется с помощью оператора точки, подобно обращению к полям класса. Поскольку перечисления являются типом данных (хоть и примитивным), мы можем создавать и использовать переменные их типа, присваивать им соответствующие значения, а также передавать их в качестве аргументов при вызове конструкторов, методов и других элементов C#.&lt;/p&gt;
  &lt;pre data-lang=&quot;clike&quot; id=&quot;ybFg&quot;&gt;Day someDay = Day.Monday; // переменная типа перечислений Day содержит
                          // значение Monday
if(someDay == Day.Monday)
{
    Console.WriteLine(&amp;quot;Today is Monday&amp;quot;);
}&lt;/pre&gt;
  &lt;p id=&quot;C0BR&quot;&gt;Внимательный читатель заметил в определении типа перечислений упоминание о том, что значения являются именованными константами целочисленного типа. Это означает, что каждая константа перечисления сопоставляется с некоторым целочисленным числом. Если мы явным образом ничего не указываем, то по умолчанию используется тип int, первому значению сопоставляется число 0, а каждому последующему значению сопоставляется число с шагом 1 (т.е. для второго значения: 1, для третьего значения: 2, для n-го значения: n - 1). Желанный целочисленный тип можно указывать явно с помощью двоеточия после имени перечисления (именно целочисленный тип!), а желанное целочисленное число, сопоставляемое со значением, с помощью оператора присваивания после конкретного значения:&lt;/p&gt;
  &lt;pre data-lang=&quot;clike&quot; id=&quot;DHpK&quot;&gt; public enum Day : long // явно указали тип long
 {
     Monday,        // Monday сопоставляется с 0 по умолчанию
     Tuesday,       // Tuesday сопоставляется с 1 по умолчанию      
     Wednesday = 5, // Wednesday сопоставляется с 5 из-за явного указания
     Thursday,      // Thursday сопоставляется с 6 по умолчанию
     Friday = 103,  // Friday сопоставляется с 103 из-за явного указания
     Saturday,      // Saturday сопоставляется с 104 по умолчанию
     Sunday         // Sunday сопоставляется с 105 по умолчанию
 }&lt;/pre&gt;
  &lt;p id=&quot;DFm7&quot;&gt;Каждому элементу перечисления можно задать значения, эти значения могут повторяться и даже являться другими элементами перечисления:&lt;/p&gt;
  &lt;pre data-lang=&quot;clike&quot; id=&quot;csbo&quot;&gt;public enum Day
{
     Monday = 1,         // Monday сопоставляется с 1
     Tuesday = 43,       // Tuesday сопоставляется с 43
     Wednesday = Monday, // Wednesday сопоставляется с 1
     Thursday,           // Thursday сопоставляется с 2
     Friday = 23,        // Friday сопоставляется с 23
     Saturday = 23,      // Saturday сопоставляется с 23
     Sunday              // Sunday сопоставляется с 24
}&lt;/pre&gt;
  &lt;p id=&quot;L58I&quot;&gt;Чтобы использовать целочисленное значения элемента перечисления, мы можем использовать операцию явного приведения:&lt;/p&gt;
  &lt;pre data-lang=&quot;clike&quot; id=&quot;7vBd&quot;&gt;Console.WriteLine((int)Day.Tuesday); // для последнего примера Day
                                     // значение будет 43&lt;/pre&gt;
  &lt;p id=&quot;FBbD&quot;&gt;Здесь стоит отметить, что константа сопоставляется с каким-то целочисленным значением, но мы не можем присвоить ей числовое значение:&lt;/p&gt;
  &lt;pre data-lang=&quot;clike&quot; id=&quot;Lxyt&quot;&gt;Day monday = 1; // Ошибка!&lt;/pre&gt;
  &lt;p id=&quot;JR9j&quot;&gt;Удобным случаем применения перечисления зачастую является связка с конструкцией &lt;code&gt;switch case&lt;/code&gt;, разобранной в предыдущей статье. Предположим, мы хотим создать метод, который на основании переданного значения типа перечисления, приветствует нас и говорит о текущем дне недели:&lt;/p&gt;
  &lt;pre data-lang=&quot;clike&quot; id=&quot;tW4r&quot;&gt;public void SayHello(Day currentDay)
{
    switch (currentDay)
    {
        case Day.Monday:
            Console.WriteLine(&amp;quot;Hi! Today is Monday.&amp;quot;);
            break;
        case Day.Tuesday:
            Console.WriteLine(&amp;quot;Hi! Today is Tuesday.&amp;quot;);
            break;
        case Day.Wednesday:
            Console.WriteLine(&amp;quot;Hi! Today is Wednesday.&amp;quot;);
            break;
        case Day.Thursday:
            Console.WriteLine(&amp;quot;Hi! Today is Thursday.&amp;quot;);
            break;
        case Day.Friday:
            Console.WriteLine(&amp;quot;Hi! Today is Friday.&amp;quot;);
            break;
        case Day.Saturday:
            Console.WriteLine(&amp;quot;Hi! Today is Saturday.&amp;quot;);
            break;
        case Day.Sunday:
            Console.WriteLine(&amp;quot;Hi! Today is Sunday.&amp;quot;);
            break;
        default:
            break;
    }
}&lt;/pre&gt;
  &lt;p id=&quot;sb3L&quot;&gt;Таким образом, заранее зная все дни недели, мы получили однозначно читаемый код, снизили вероятность получить ошибку из-за опечатки путем строгого ограничения работоспособности метода на типе перечисления.&lt;/p&gt;
  &lt;p id=&quot;uASR&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;bVTt&quot;&gt;На этом все, что было запланировано на эту статью. Далее разберем тему с массивов и начнем знакомиться с коллекциями. Ставьте лойсы под постом, подписывайтесь на наш &lt;a href=&quot;https://t.me/serious_seesharp&quot; target=&quot;_blank&quot;&gt;несерьезный канал&lt;/a&gt; в телеграме. Всем хорошего настроения и продуктивного изучения самого лучшего языка!&lt;/p&gt;

</content></entry><entry><id>notserious_seesharp:control_statements</id><link rel="alternate" type="text/html" href="https://teletype.in/@notserious_seesharp/control_statements?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=notserious_seesharp"></link><title>Управляющие конструкции</title><published>2022-10-19T19:48:12.637Z</published><updated>2024-01-05T19:04:34.886Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/3a/71/3a715b7e-4178-44e9-b162-9935b5eae45b.png"></media:thumbnail><category term="osnovy-c" label="Основы C#"></category><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/d9/86/d98655c9-807e-4d12-bd6e-660bf7bc158e.png&quot;&gt;Привет! Мы продолжаем нашу серию статей по основам C#. Сегодня разберемся с приоритетом операций и управляющими конструкциями. Чего-то сильно уникального относительно других языков вы вряд ли для себя найдете, если C# для вас не первый язык, но можете обратить внимание на оператор switch, он действительно устроен немного по-другому.</summary><content type="html">
  &lt;p id=&quot;2VX7&quot;&gt;Привет! Мы продолжаем нашу серию статей по основам C#. Сегодня разберемся с приоритетом операций и управляющими конструкциями. Чего-то сильно уникального относительно других языков вы вряд ли для себя найдете, если C# для вас не первый язык, но можете обратить внимание на оператор &lt;code&gt;switch&lt;/code&gt;, он действительно устроен немного по-другому.&lt;/p&gt;
  &lt;figure id=&quot;Ztu4&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d9/86/d98655c9-807e-4d12-bd6e-660bf7bc158e.png&quot; width=&quot;945&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;Erhz&quot;&gt;Операторы и выражения&lt;/h3&gt;
  &lt;p id=&quot;sqbn&quot;&gt;&lt;em&gt;При прочтении различных книжек на русском вы сможете заметить, что в разных работах даны свои определения операциям, операторам и выражениям в C#, и при этом они могут еще и противоречить друг другу, из-за чего возникает некоторая несущественная путаница в понятиях. Вероятно, это связано с некорректным переводом с английского. Я постараюсь дать определения в соответствии с тем, как это преподносит нам microsoft.&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;NIOI&quot;&gt;Итак, основной код программы на C# состоит из инструкций/команд(statements), операторов(operators) и выражений(expressions). &lt;/p&gt;
  &lt;p id=&quot;BQ0g&quot;&gt;Инструкции могут состоять из одной строки кода, заканчивающейся точкой с запятой, или серии однострочных инструкций в блоке. Блок инструкций заклюается в скобки &lt;code&gt;{}&lt;/code&gt; и может содержать вложенные блоки.&lt;/p&gt;
  &lt;pre id=&quot;wQp3&quot; data-lang=&quot;clike&quot;&gt;//пример простой инструкции объявления переменной
int a = 5;&lt;/pre&gt;
  &lt;p id=&quot;7R2I&quot;&gt;Инструкции состоят из выражений, а те, в свою очередь – из операндов и операторов. Каждая инструкция заканчивается &lt;code&gt;;&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;I0NT&quot;&gt;Операция - это некоторое действие, производимое над операндами. А оператор определяет суть данного действия, которое будет произведено над операндами (например, оператор &lt;code&gt;+&lt;/code&gt; определяет действие сложения).&lt;/p&gt;
  &lt;p id=&quot;zg15&quot;&gt;Выражение же обозначает по существу значение, которое мы можем получить в результате его выполнения. Простейшими выражениями являются литералы и переменные, которые можно объединять в сложные выражения с помощью операторов.&lt;/p&gt;
  &lt;pre id=&quot;fq6Y&quot; data-lang=&quot;clike&quot;&gt;//инструкции объявления переменных
int a;
int b = 1; //правая часть rvalue является константным выражением

//инструкция выражения, сохраняющая значение выражение в переменной a
a = 1 + b + 2;
//1 + b + 2 - выражение
//1, b, 2 - операнды
//+ = - операторы&lt;/pre&gt;
  &lt;p id=&quot;OFAs&quot;&gt;Операторы в C# делятся на унарные, бинарные и тернарные в зависимости от количества операндов, с которыми они работают. &lt;/p&gt;
  &lt;p id=&quot;GYUf&quot;&gt;При вычислении выражения, в котором содержится несколько операторов, операции выполняются в порядке приоритета операторов. &lt;/p&gt;
  &lt;figure id=&quot;WTKw&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f6/ab/f6abc25c-0268-42c0-b988-30d60160f253.png&quot; width=&quot;633&quot; /&gt;
    &lt;figcaption&gt;В таблице перечислены все операторы C#, начиная с наивысшего приоритета и заканчивая низшим. Операторы одной категории имеют одинаковый приоритет&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;pre id=&quot;2UCy&quot; data-lang=&quot;clike&quot;&gt;int a = 5 + 4 * 3;
// 5 + (4 * 3) = 17 &lt;/pre&gt;
  &lt;p id=&quot;fFXo&quot;&gt;Если в выражении операции имеют одинаковые приоритеты, то порядок их выполнения определяется ассоциативностью. Операторы делятся на л&lt;em&gt;евоассоциативные&lt;/em&gt; и п&lt;em&gt;равоассоциативные.&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;FXjR&quot;&gt;Левоассоциативные операции выполняются последовательно слева направо. Левоассоциативными операторами являются все бинарные операторы за исключением операторов присвоения, объединения с null.&lt;/p&gt;
  &lt;pre id=&quot;qhfn&quot; data-lang=&quot;clike&quot;&gt;1 + 2 + 3 //((1 + 2) + 3) = 3 + 3 = 6&lt;/pre&gt;
  &lt;p id=&quot;h0I2&quot;&gt;Правоассоциативные операции выполняются справа налево. Правоасоциативными являются операторы присваивания, объединения с &lt;code&gt;null&lt;/code&gt;, лямбда-выражения и тернарный оператор&lt;code&gt;?:&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;Q85V&quot; data-lang=&quot;clike&quot;&gt;int a = 5;
int b, c;
b = c = a; //b = (c = a)&lt;/pre&gt;
  &lt;h3 id=&quot;ufSm&quot;&gt;&lt;br /&gt;Управляющие конструкции&lt;/h3&gt;
  &lt;p id=&quot;XlqA&quot;&gt;Программы не ограничиваются линейной последовательностью выполнения команд, что было показано до этого. Во время выполнения программа может повторять сегменты кода или разветвляться в зависимости от некоторого условия. Для этих целей в C# существует ряд конструкций для управления потоком выполнения программы, которые служат для указания, что наша программа должна сделать, когда и при каких обстоятельствах:&lt;/p&gt;
  &lt;ul id=&quot;7f8I&quot;&gt;
    &lt;li id=&quot;ZeWT&quot;&gt;условные конструкции (&lt;code&gt;if else&lt;/code&gt;)&lt;/li&gt;
    &lt;li id=&quot;xJQY&quot;&gt;конструкция выбора (&lt;code&gt;switch&lt;/code&gt;)&lt;/li&gt;
    &lt;li id=&quot;Rx5a&quot;&gt;конструкции цикла (&lt;code&gt;while&lt;/code&gt;, &lt;code&gt;do while&lt;/code&gt;, &lt;code&gt;for&lt;/code&gt;, &lt;code&gt;foreach&lt;/code&gt;)&lt;/li&gt;
    &lt;li id=&quot;i7ds&quot;&gt;условный оператор (?:)&lt;/li&gt;
    &lt;li id=&quot;hxCb&quot;&gt;выражение (&lt;code&gt;switch&lt;/code&gt;)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;0ezS&quot;&gt;&lt;em&gt;Вообще в документации и в оригинальной литературе конструкции &lt;code&gt;if else&lt;/code&gt;, &lt;code&gt;switch,&lt;/code&gt; &lt;code&gt;for&lt;/code&gt;, &lt;code&gt;while, break, continue&lt;/code&gt; выделяются от operators именно как инструкции statements, но во всех переводах мы увидим заветное слово &amp;quot;оператор&amp;quot; в независимости &lt;code&gt;+&lt;/code&gt; это или &lt;code&gt;if&lt;/code&gt;. Не знаю, только у меня такая шиза или еще кого-то от этих понятий триггерит. Наверно нужно это принять... &lt;/em&gt;&lt;/p&gt;
  &lt;figure id=&quot;709Y&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/8e/cc/8ecc4af4-3a4e-455f-94b4-c052255345a8.jpeg&quot; width=&quot;600&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;QInq&quot;&gt;&lt;strong&gt;Условная конструкция if else&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;hBLZ&quot;&gt;Конструкция if проверяет истинность некоторого условия и в зависимости от результатов проверки выполняет определенный код. Синтаксис выглядит следующим образом:&lt;/p&gt;
  &lt;pre id=&quot;xv9k&quot; data-lang=&quot;clike&quot;&gt;if (условие)
{
    блок выполнения, если условие истинно
}
else
{
    блок выполнения, если условие ложно
}&lt;/pre&gt;
  &lt;p id=&quot;RV63&quot;&gt;После ключевого слова &lt;code&gt;if&lt;/code&gt; следует логическое выражение, на основе которого происходит выбор выполняющегося кода. Код в блоке if будет выполнен в том случае, если выражение дает значение &lt;code&gt;true&lt;/code&gt;, а блок else выполняться не будет, и наоборот.&lt;/p&gt;
  &lt;pre id=&quot;kVbr&quot; data-lang=&quot;clike&quot;&gt;int a = 1;
int b = 5;

if (a &amp;gt; b)
{
    Console.WriteLine($&amp;quot;First number {a} is greater {b}&amp;quot;);
}
else
{
    Console.WriteLine($&amp;quot;First number {a} is not greater {b}&amp;quot;);
}&lt;/pre&gt;
  &lt;p id=&quot;2TTM&quot;&gt;Часть &lt;code&gt;else&lt;/code&gt; является необязательной, и при ее отсутствии будет выполняться только блок &lt;code&gt;if&lt;/code&gt;, если условие истинно.&lt;/p&gt;
  &lt;pre id=&quot;MJLQ&quot; data-lang=&quot;clike&quot;&gt;int a = 20;
int b = 20;

if (a == b)
{
    Console.WriteLine($&amp;quot;Numbers are equal&amp;quot;);
}&lt;/pre&gt;
  &lt;p id=&quot;V6En&quot;&gt;Но выполнении программы может возникнуть ситуация, когда нам необходимо выполнить более одного разветвления. Например, в первом примере при сравнении двух переменных может быть три варианта развития событий: когда первая больше второй, когда вторая больше и когда они равны; Чтобы поправить пример и корректно обработать все три случая мы можем в блоке &lt;code&gt;else&lt;/code&gt; прописать еще одно сравнение &lt;code&gt;if&lt;/code&gt; (что делать не нужно), а можем использовать конструкцию &lt;code&gt;else if&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;n1IY&quot; data-lang=&quot;clike&quot;&gt;int a = 1;
int b = 5;

/*if (a &amp;gt; b)
{
    Console.WriteLine($&amp;quot;First number {a} is greater {b}&amp;quot;);
}
else
{
    if (a == b)
    {
        Console.WriteLine($&amp;quot;Numbers are equal&amp;quot;);
    }
    else
    {
        Console.WriteLine($&amp;quot;First number {a} is less {b}&amp;quot;);
    }
}*/

if (a &amp;gt; b)
{
    Console.WriteLine($&amp;quot;First number {a} is greater {b}&amp;quot;);
}
else if (a == b)
{
    Console.WriteLine($&amp;quot;Numbers are equal&amp;quot;);
}
else
{
    Console.WriteLine($&amp;quot;First number {a} is less {b}&amp;quot;);
}&lt;/pre&gt;
  &lt;p id=&quot;LgXm&quot;&gt;И, да, когда в блоке конструкции одна команда, можно опустить скобки &lt;code&gt;{}&lt;/code&gt;, они необходимы, когда в блоке содержится более одной команды.&lt;/p&gt;
  &lt;pre id=&quot;Zknh&quot; data-lang=&quot;clike&quot;&gt;if (a &amp;gt; b)
    Console.WriteLine($&amp;quot;First number {a} is greater {b}&amp;quot;);&lt;/pre&gt;
  &lt;p id=&quot;nZt1&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;tP4N&quot;&gt;Сравнение ссылочных типов&lt;/h3&gt;
  &lt;p id=&quot;aUU3&quot;&gt;Как мы уже знаем, для сравнения двух значений на равенство используется оператор ==. И это действительно справедливо для типов значений, операнды типов значений равны, если равны их значения.&lt;/p&gt;
  &lt;pre id=&quot;35j7&quot; data-lang=&quot;clike&quot;&gt;int a = 5;
int b = 5;

Console.WriteLine(a == b); //true&lt;/pre&gt;
  &lt;p id=&quot;pHpp&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;riY6&quot;&gt;При сравнении же операндов ссылочного типа через оператор == по умолчанию будут сравниваться ссылки на данные объекты. Если два операнда ссылаются на один и тот же объект в памяти, будет &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;K3YW&quot; data-lang=&quot;clike&quot;&gt;class Cat
{
    public int id;
}

Cat cat1 = new Cat{ id=1 };
Cat cat2 = new Cat{ id=1 };
Cat cat3 = cat1;

Console.WriteLine(cat1 == cat2); //false
Console.WriteLine(cat1 == cat3); //true&lt;/pre&gt;
  &lt;p id=&quot;4JQp&quot;&gt;Для определения условия сравнения объектов у ссылочного типа используется метод &lt;code&gt;Equals()&lt;/code&gt;, который возвращает значение типа bool и наследуется от базового класса &lt;code&gt;Object&lt;/code&gt;. &lt;/p&gt;
  &lt;blockquote id=&quot;gD1o&quot;&gt;Да-да, я еще не рассказывал про наследование и это будет далее. Пока просто нужно понимать, что все создаваемые пользовательские классы неявно наследуются от общего базового класса &lt;code&gt;Object&lt;/code&gt;, а вместе с ним его методы, которые мы можем переопределить в своих классах.&lt;/blockquote&gt;
  &lt;p id=&quot;sK0n&quot;&gt;По умолчанию метод Equals также проверяет равенство ссылок объектов, но это поведение можно изменить перегрузив данный метод у класса.&lt;/p&gt;
  &lt;pre id=&quot;0FFK&quot; data-lang=&quot;clike&quot;&gt;class Cat
{
    public int id;
    
    public bool Equals(Cat otherCat)
    {
        return id == otherCat.id;
    }
}

Cat cat1 = new Cat{ id=1 };
Cat cat2 = new Cat{ id=1 };
Cat cat3 = cat1;

Console.WriteLine(cat1 == cat2); //false
Console.WriteLine(cat1 == cat3); //true
Console.WriteLine(cat1.Equals(cat2)); //true
Console.WriteLine(cat1.Equals(cat3)); //true&lt;/pre&gt;
  &lt;blockquote id=&quot;FViK&quot;&gt;У вас сразу же может возникнуть вопрос. Раз String - это класс и он, соответственно, является ссылочным типом, то почему же строки нормально сравниваются по значению также и через оператор == ?! А все просто, для строк == также перегружен для их сравнения не по ссылке. О перегрузке операторов мы также уже скоро поговорим) &lt;/blockquote&gt;
  &lt;h3 id=&quot;t54R&quot;&gt;&lt;br /&gt;Тернарный оператор&lt;/h3&gt;
  &lt;p id=&quot;v91G&quot;&gt;Условный оператор (тернарный) вычисляет логическое выражение и в зависимости от полученного значения &lt;code&gt;true&lt;/code&gt; или &lt;code&gt;false&lt;/code&gt; возвращает результат одного из двух соответствующих выражений. Он имеет следующий синтаксис:&lt;/p&gt;
  &lt;p id=&quot;nMsf&quot;&gt;&lt;code&gt;[условие] ? [выражение при истинном условии] : [выражение при ложном условии]&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;TUZa&quot;&gt;В зависимости от значения логического(условного) выражения &lt;code&gt;true&lt;/code&gt; или &lt;code&gt;false&lt;/code&gt;, будет выполняться первое или второе выражение тернарного оператора.&lt;/p&gt;
  &lt;pre id=&quot;DtOw&quot; data-lang=&quot;clike&quot;&gt;int a = 1;
int b = 5; 

int c = a &amp;lt; b ? a + b : a - b;
Console.WriteLine(c); //6&lt;/pre&gt;
  &lt;p id=&quot;BvVQ&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;hem7&quot;&gt;Циклы&lt;/h3&gt;
  &lt;p id=&quot;gCfc&quot;&gt;Циклы являются управляющими конструкциями, позволяющими выполнять набор инструкций множество раз. В C# 4 типа циклов: &lt;code&gt;for&lt;/code&gt;, &lt;code&gt;foreach&lt;/code&gt;, &lt;code&gt;while&lt;/code&gt;, &lt;code&gt;do while&lt;/code&gt;. В этой статье пойдет речь только о 3 штуках. О &lt;code&gt;foreach&lt;/code&gt; мы поговорим, когда будем разбирать коллекции.&lt;/p&gt;
  &lt;p id=&quot;AkaC&quot;&gt;&lt;strong&gt;Цикл for&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;XH1K&quot;&gt;Цикл &lt;code&gt;for&lt;/code&gt; имеет следующее формальное определение:&lt;/p&gt;
  &lt;pre id=&quot;EwZ9&quot; data-lang=&quot;clike&quot;&gt;for ([действия_до_выполнения_цикла]; [условие]; [действия_после_выполнения])
{
    // блок команд
}&lt;/pre&gt;
  &lt;p id=&quot;Xmoc&quot;&gt;Объявление цикла for состоит из трех частей. Первая часть объявления цикла - некоторые действия, которые выполняются один раз до выполнения цикла. Обычно здесь определяются переменные, которые будут использоваться в цикле.&lt;/p&gt;
  &lt;p id=&quot;ptVN&quot;&gt;Вторая часть - условие, при котором будет выполняться цикл. Цикл будет выполняться, пока условие не станет равно &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;HpFG&quot;&gt;И третья часть - некоторые действия, которые выполняются после завершения блока цикла. Эти действия выполняются каждый раз при завершении блока цикла.&lt;/p&gt;
  &lt;p id=&quot;K9Dg&quot;&gt;Но при этом все эти 3 части являются необязательными и их при надобности можно опустить)&lt;/p&gt;
  &lt;pre id=&quot;YoK7&quot; data-lang=&quot;clike&quot;&gt;int sum = 0;
for (int i = 0; i &amp;lt; 5; i++)
{
    sum += i;
}

Console.WriteLine(sum); //10

for (int i = 1, int j = 2; i &amp;lt; 10; i++, j++)
{
    int mult = i * j;
    Console.WriteLine($&amp;quot;{mult}&amp;quot;);
}

int i = 1;
for (; ;) //этот цикл будет бесконечным, что не гуд, нужно делать точку выхода из цикла
{
    Console.WriteLine($&amp;quot;i = {i}&amp;quot;);
    i++;
}&lt;/pre&gt;
  &lt;p id=&quot;z43c&quot;&gt;Все переменные, которые создаются при объявлении цикла (как i j в примере) являются локальными переменными в области видимости данного цикла, а переменные, которые создаются в теле цикла живут в течении одной итерации.&lt;/p&gt;
  &lt;p id=&quot;Zea3&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;gkT1&quot;&gt;&lt;strong&gt;Циклы while и do while&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;y5up&quot;&gt;Цикл &lt;code&gt;while&lt;/code&gt; выполняет блок команд, пока определенное логическое выражение равно значению &lt;code&gt;true&lt;/code&gt;. Так как это выражение оценивается перед каждым выполнением цикла, цикл &lt;code&gt;while&lt;/code&gt; выполняется ноль или несколько раз. Это основное отличие от цикла &lt;code&gt;do while&lt;/code&gt; с постусловием, у которого будет выполнена хотябы одна итерация, после чего будет произведена проверка условия.&lt;/p&gt;
  &lt;pre id=&quot;bSZ0&quot; data-lang=&quot;clike&quot;&gt;int i = 10;
while (i &amp;gt; 0)
{
     Console.WriteLine(i);
     i--;
}

do
{
    Console.WriteLine(i);
    i--;
} while(i &amp;gt; 0)&lt;/pre&gt;
  &lt;blockquote id=&quot;qFYt&quot;&gt;Цикл do while почти никогда не используют, так как почти всегда есть возможность заменить его на while. На практике у меня возникала одна ситуация, когда мне потребовался именно цикл do while, и не было возможности заменить его обычным while, и то я эту ситуацию уже даже не помню))&lt;/blockquote&gt;
  &lt;p id=&quot;K3a1&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;JWxg&quot;&gt;&lt;strong&gt;Инструкции перехода: continue и break&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;lDKC&quot;&gt;Для досрочного выхода из цикла, не дожидаясь его завершения, используется &lt;code&gt;break&lt;/code&gt;. Он завершает ближайшую итерацию цикла и передает поток управления на команду, которая расположена после завершения цикла.&lt;/p&gt;
  &lt;pre id=&quot;Ljqh&quot; data-lang=&quot;clike&quot;&gt;int i = 10;
while (true)
{
     if (i &amp;lt; 0)
         break;
         
     Console.WriteLine(i);
     i--;
}&lt;/pre&gt;
  &lt;p id=&quot;aULR&quot;&gt;А для досрочного перехода к следующей итерации цикла без его завершения используется инструкция &lt;code&gt;continue&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;NXjI&quot; data-lang=&quot;clike&quot;&gt;for (int i = 0; i &amp;lt; 9; i++)
{
    if (i == 5)
        continue;
        
    Console.WriteLine(i);
}&lt;/pre&gt;
  &lt;p id=&quot;B03R&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Конструкция switch case&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;T0WH&quot;&gt;Конструкция &lt;code&gt;switch case&lt;/code&gt; позволяют организовать ветвление потока выполнения программы на основе выбора из заранее определенного набора вариантов значений, которые может принять выражение. &lt;/p&gt;
  &lt;pre id=&quot;wMgt&quot;&gt;switch (выражение)
{
    case значение1:
        набор команд, выполняемых, если выражение имеет значение1
        break;
    case значение2:
        набор команд
        break;
   case значениеN:
        набор команд
        break;
   default:
        набор команд, который выполняется, если выражение не подходит ни под одно значение выше
        break;
}&lt;/pre&gt;
  &lt;p id=&quot;Kq9b&quot;&gt;После ключевого слова &lt;code&gt;switch&lt;/code&gt; в скобках идет сравниваемое выражение. Значение этого выражения последовательно сравнивается со значениями, помещенными после оператора &lt;code&gt;сase&lt;/code&gt;. И если совпадение будет найдено, то будет выполняться определенный блок &lt;code&gt;сase&lt;/code&gt;. Если значение выражения не подходит ни под одно условие, будет выполнен блок &lt;code&gt;default&lt;/code&gt;, но он в конструкции необязателен.&lt;/p&gt;
  &lt;p id=&quot;lIyS&quot;&gt;В конце каждого блока &lt;code&gt;case&lt;/code&gt; следует указывать &lt;code&gt;break&lt;/code&gt;, с помощью которого произойдет выход из контекста switch после выполнения всех инструкций данного case блока. А иначе сравниваться будут последовательно все блоки case, даже если &amp;quot;совпадение&amp;quot; уже было найдено.&lt;/p&gt;
  &lt;p id=&quot;lv1q&quot;&gt;switch в C# способен сопоставлять все базовые типы данных &lt;code&gt;char&lt;/code&gt;, &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;long&lt;/code&gt;, &lt;code&gt;float&lt;/code&gt;, &lt;code&gt;double&lt;/code&gt;, &lt;code&gt;decimal&lt;/code&gt; и &lt;code&gt;enum&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;s7GL&quot; data-lang=&quot;clike&quot;&gt;int x = 4;

switch (x)
{ 
     case 1: 
         Console.WriteLine(&amp;quot;one&amp;quot;); 
         break; 
     case 2: 
         Console.WriteLine (&amp;quot;two&amp;quot;); 
         break; 
     case 3: 
         Console.WriteLine (&amp;quot;three&amp;quot;); 
         break; 
     case 4: 
         Console.WriteLine (&amp;quot;four&amp;quot;); 
         break;
     default:
         Console.Writeline(&amp;quot;Wow, king in the castle&amp;quot;);
         break;
}&lt;/pre&gt;
  &lt;p id=&quot;ML4V&quot;&gt;До выхода версии C# 7 сопоставляющие выражения в операторах switch ограничивались сравнением переменной с константными значениями. Начиная с C# 7 появилась возможность указывать диапазон значений, с помощью ключевого слова &lt;code&gt;when&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;mH8a&quot; data-lang=&quot;clike&quot;&gt;int x = 6;

switch (x)
{
    case &amp;lt; 5:
        Console.WriteLine(&amp;quot;&amp;lt; 6&amp;quot;);
        break;
    case int i when i &amp;gt; 5 &amp;amp;&amp;amp; i &amp;lt; 6:
        Console.WriteLine(&amp;quot;5 &amp;lt; x &amp;lt; 6&amp;quot;);
        break;
    case int i when i &amp;gt; 5 || i == 4:
        Console.WriteLine(&amp;quot;&amp;gt; 5 || 4&amp;quot;);
        break;
    case 6:
        Console.WriteLine(&amp;quot;6&amp;quot;);
        break;
}&lt;/pre&gt;
  &lt;p id=&quot;VBvF&quot;&gt;&lt;em&gt;Привел глупый пример, но как работать &lt;code&gt;when&lt;/code&gt; должно стать понятно)&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;Hz49&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;XmK9&quot;&gt;На этом вроде все) Не стал писать здесь про весьма приятное выражение &lt;code&gt;switch&lt;/code&gt;, о нем вероятно напишу в отдельном посте. И не стал писать про &lt;code&gt;go to&lt;/code&gt;, прости господи. Не используйте недоразумение в своем коде, тех, кто использует это недоразумение, бьют палками!!! Нет таких ситуаций, когда нельзя было бы не обойтись без go to.&lt;/p&gt;
  &lt;p id=&quot;MHKm&quot;&gt;В следующей статье снова поговорим о классах. Рассмотрим свойства, конструкторы и деструкторы классов, жизненный цикл объекта класса и немного поговорим про garbage collector и возможно еще о чем-нибудь)&lt;br /&gt;Подписывайтесь на наш &lt;a href=&quot;https://t.me/serious_seesharp&quot; target=&quot;_blank&quot;&gt;тг-канал&lt;/a&gt;, чтобы ничего не пропустить.&lt;/p&gt;

</content></entry><entry><id>notserious_seesharp:classes_basic</id><link rel="alternate" type="text/html" href="https://teletype.in/@notserious_seesharp/classes_basic?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=notserious_seesharp"></link><title>Классы, поля и методы</title><published>2022-10-12T16:16:34.628Z</published><updated>2023-02-25T01:26:23.929Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/e0/76/e0761b5e-1ad3-4fb0-b1a3-59a162fa09a5.png"></media:thumbnail><category term="osnovy-c" label="Основы C#"></category><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/2f/8f/2f8fa068-933b-469b-be42-8a23f1c87556.png&quot;&gt;В данной статье мы поговорим о классах и объектах, их идеологических особенностях и об основе работы с ними. Также разберемся с тем, что такое типы значений и ссылочные типы. Погнали)</summary><content type="html">
  &lt;p id=&quot;PenA&quot;&gt;В данной статье мы поговорим о классах и объектах, их идеологических особенностях и об основе работы с ними. Также разберемся с тем, что такое типы значений и ссылочные типы. Погнали)&lt;/p&gt;
  &lt;figure id=&quot;OgFr&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/2f/8f/2f8fa068-933b-469b-be42-8a23f1c87556.png&quot; width=&quot;1200&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;CVMI&quot;&gt;Классы и объекты&lt;/h3&gt;
  &lt;p id=&quot;wvhG&quot;&gt;C# является объектно-ориентированным языком программирования. В нем также присутствуют элементы подхода функционального программирования, что делает его гибридным, но основной &amp;quot;фундамент&amp;quot; языка завязан именно на ООП, что означает, что при написании кода мы постоянно будем сталкиваться с классами и объектами классов.&lt;/p&gt;
  &lt;p id=&quot;yukY&quot;&gt;Объектно-ориентированное программирование - это подход программирования, при котором программа представляется, как совокупность объектов, имеющих свое состояние и поведение и взаимодействующих друг с другом.&lt;/p&gt;
  &lt;p id=&quot;jK0J&quot;&gt;Класс — это некий общий шаблон, на основании которого создаются объекты, определяющий набор данных и поведение(методы), которым будут обладать эти объекты.&lt;/p&gt;
  &lt;p id=&quot;vqMO&quot;&gt;Под объектом же (экземпляром класса) мы подразумеваем некоторую отдельную сущность, которая получается в результате создания по данному шаблону (классу) и обладает своим внутренним состоянием и поведением, определяемым классом.&lt;/p&gt;
  &lt;p id=&quot;bILG&quot;&gt;Разберем на примере машины. &lt;br /&gt;Пусть у нашего условного класса &amp;quot;машина&amp;quot; будет набор данных: &amp;quot;наименование&amp;quot;, &amp;quot;номер&amp;quot;, &amp;quot;тип машины&amp;quot;, &amp;quot;количество колес&amp;quot;, &amp;quot;максимальная скорость&amp;quot;. И у класса будет определено поведение из методов: &amp;quot;ехать вперед с максимальной скоростью&amp;quot;, &amp;quot;ехать назад&amp;quot;, &amp;quot;открыть дверь&amp;quot;.&lt;br /&gt;Но наша &amp;quot;машина&amp;quot; лишь описывает набор свойств и общее поведение некоторого класс автомобилей, а уже создаваемые по этому описанию объекты автомобилей будут задавать свое собственное состояние.&lt;/p&gt;
  &lt;figure id=&quot;hWqF&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/be/43/be439cb3-3b38-4f70-b06b-e78f94028ca7.png&quot; width=&quot;1200&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;jjQU&quot;&gt;Таким образом, класс — это описание того, какими свойствами и поведением будет обладать объект. А объекты — это экземпляры класса с собственным состоянием (т.е. все экземпляры имеют одинаковый набор свойств, но их значения могут быть разными).&lt;/p&gt;
  &lt;p id=&quot;mdL4&quot;&gt;Синтаксис объявления класса выглядит следующим образом:&lt;/p&gt;
  &lt;pre id=&quot;p3HV&quot; data-lang=&quot;clike&quot;&gt;class ИмяКласса
{
    //тело класса
}&lt;/pre&gt;
  &lt;p id=&quot;fNZ5&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;bKfN&quot;&gt;Типы значений и ссылочные типы&lt;/h3&gt;
  &lt;p id=&quot;dNiW&quot;&gt;В .NET память делится на два основных типа: стек и куча. Стек представляет собой структуру данных, которая растет снизу вверх: каждый новый добавляемый элемент помещается поверх предыдущего, а физически - это зарезервированная для программы, при ее запуске, область памяти. Кучей же (heap&amp;#x27;ом) является остальное адресное пространство памяти, доступное процессору.&lt;/p&gt;
  &lt;p id=&quot;U88G&quot;&gt;Все что нам на данном этапе нужно знать так это то, что выделяемый размер стека программы довольно мал и обычно равен 1 мб и используется для быстрого доступа к небольшим данным программы, а в куче мы можем хранить неупорядоченно большие объекты, ограничиваясь лишь объемом оперативной памяти.&lt;/p&gt;
  &lt;p id=&quot;NYJE&quot;&gt;Типы значений - примитивы, можно описать как &amp;quot;неделимые&amp;quot; типы данных, располагающиеся в памяти в зависимости от контекста, обычно на стеке. К типам значений относятся: &lt;code&gt;byte, sbyte, short, ushort, int, uint, long, ulong, float, double, decimal, bool, char&lt;/code&gt;. Также к типам значений относятся struct и enum, но о них мы поговорим немного позже:)&lt;/p&gt;
  &lt;p id=&quot;Co0K&quot;&gt;Ссылочные типы - типы, состоящие из других типов данных, в том числе и из примитивов. Значением ссылочного типа является ссылка на объект в выделенной под него области памяти (в куче). Несколько переменных ссылочного типа могут ссылаться на один и тот же объект, при изменении объекта с помощью одной переменной, изменения будут также естественно отражены во всех остальных. &lt;/p&gt;
  &lt;p id=&quot;71mH&quot;&gt;Все классы относятся к ссылочным типам. Кстати тип string на самом деле является псевдонимом класса String, а, соответственно, тоже является классом. Но выделение памяти при создании строк в .Net происходит немного иначе, нежели для обычных классов, мы рассмотрим это, когда будем подробно говорить о работе со строками.&lt;/p&gt;
  &lt;p id=&quot;Urmo&quot;&gt;У типов значений и ссылочных типов есть еще ряд отличий, но перечисленной информации пока достаточно для понимания рассматриваемого материала. А это нам и надо, чтобы все было по полочкам). Так что, пока стоит запомнить, что: &lt;/p&gt;
  &lt;ol id=&quot;ZAP8&quot;&gt;
    &lt;li id=&quot;g5Kj&quot;&gt;Ссылочные типы хранятся в куче, а на стеке для них выделяется ссылка на данную область памяти.&lt;/li&gt;
    &lt;li id=&quot;3FOU&quot;&gt;Типы значений являются примитивами и хранятся в зависимости от контекста на стеке или в куче. (Обычно типы значений хранятся на стеке, но никто не запрещает нам взять и выделить память под тот же &lt;code&gt;int&lt;/code&gt; в куче)&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;4Bba&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;9wsk&quot;&gt;Создание объектов класса&lt;/h3&gt;
  &lt;p id=&quot;4h1d&quot;&gt;При объявлении переменных, если не проинициализировать их, то они будут иметь определенные дефолтные значения. Для типов значений дефолтными значениями являются: целочисленные и типы с плавающей точкой - &lt;code&gt;0&lt;/code&gt;, bool - &lt;code&gt;false&lt;/code&gt;, char - &lt;code&gt;&amp;#x27;\0&amp;#x27;&lt;/code&gt;. Ссылочные типы имеют в качестве значения по умолчанию пустую ссылку &lt;code&gt;null&lt;/code&gt; (напомню, их переменные на стеке хранят именно ссылку на область в памяти, а так как объект не был создан, то и в памяти объекта еще нет).&lt;/p&gt;
  &lt;pre id=&quot;x8ks&quot; data-lang=&quot;clike&quot;&gt;int a;              //0
bool cond;          //false
ExampleRobot robot; //null

class ExampleRobot {}&lt;/pre&gt;
  &lt;p id=&quot;igRd&quot;&gt;Для создания нового экземпляра класса используется оператор &lt;code&gt;new,&lt;/code&gt; который выполняет следующие действия:&lt;/p&gt;
  &lt;ol id=&quot;x1cm&quot;&gt;
    &lt;li id=&quot;INr5&quot;&gt;Вычисление необходимого количества памяти;&lt;/li&gt;
    &lt;li id=&quot;EuKl&quot;&gt;Выделение памяти для объекта;&lt;/li&gt;
    &lt;li id=&quot;7hXW&quot;&gt;Инициализация указателя на объект;&lt;/li&gt;
    &lt;li id=&quot;IeW4&quot;&gt;Вызов конструктора экземпляра. (про конструкторы, деструкторы и жизненный цикл объектов будет материал далее, все идет своим чередом:)&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;00hX&quot;&gt;Синтаксис создания объекта выглядит следующим образом:&lt;/p&gt;
  &lt;pre id=&quot;mWrs&quot;&gt;ExampleRobot robot = new ExampleRobot();&lt;/pre&gt;
  &lt;p id=&quot;d6zx&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;LIpO&quot;&gt;Поля и методы класса&lt;/h3&gt;
  &lt;p id=&quot;Fe8r&quot;&gt;Для хранения данных в классе применяются поля - переменные, определенные на уровне класса. (В примерах сейчас будет использоваться спецификатор доступа &lt;code&gt;public&lt;/code&gt;, мы это разбираем немного ниже)&lt;/p&gt;
  &lt;pre id=&quot;naaG&quot; data-lang=&quot;clike&quot;&gt;class Robot
{
    public int id;
    public string model;
    public string name = &amp;quot;fuckhead&amp;quot;;
}&lt;/pre&gt;
  &lt;p id=&quot;IFMS&quot;&gt;Как говорилось выше для задания поведения для объектов класса используются методы. Метод - это блок кода, объединяющий набор команд, которые выполняются при его вызове. Методы могут возвращать либо не возвращать значение. Методы, которые ничего не возвращают имеют тип возвращаемого значения &lt;code&gt;void&lt;/code&gt;. Для возврата значения используется оператор &lt;code&gt;return&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;mlKi&quot;&gt;Мы также можем принимать в методах класса некоторый набор параметров, которые будут использоваться при выполнении данного метода.&lt;/p&gt;
  &lt;pre id=&quot;BgJy&quot; data-lang=&quot;clike&quot;&gt;class Robot
{
    public int id;
    public string model;
    public string name = &amp;quot;fuckhead&amp;quot;;
    
    public void Dance()
    {
        Console.WriteLine($&amp;quot;I&amp;#x27;m {name} robot, I will dance&amp;quot;);
    }
    
    public int MultiplyWithId(int a, int b)
    {
        int c = a * b * id;
        return c;
    }
}&lt;/pre&gt;
  &lt;p id=&quot;Md0i&quot;&gt;Данные поля и методы определяют состояние создаваемого объекта класса и могут влиять на него. Соответственно, чтобы нам обратиться к этому функционалу, который определяет состояние объекта, нам необходимо создать этот экземпляр класса. Для обращения к полям и методам объекта используется оператор точка &lt;code&gt;.&lt;/code&gt;&lt;/p&gt;
  &lt;pre id=&quot;IfAr&quot; data-lang=&quot;clike&quot;&gt;Robot ironMan = new Robot();

ironMan.Dance(); //I&amp;#x27;m fuckhead robot, I will dance

int c = ironMan.MultiplyWithId(1, 2); //0
ironMan.id = 5;
c = ironMan.MultiplyWithId(1, 2); //10&lt;/pre&gt;
  &lt;h3 id=&quot;eAGm&quot;&gt;&lt;/h3&gt;
  &lt;p id=&quot;tZMv&quot;&gt;Для инициализации полей объекта класса мы можем, задать некоторое дефолтное значение поля, которое будет использоваться при инициализации объекта, можно обратиться к полям напрямую через имя объекта и присвоить какое-то значение. Также можно использовать инициализаторы объекта, позволяющие при создании объекта проинициализировать поля нужными значениями.&lt;/p&gt;
  &lt;pre id=&quot;g7Xo&quot; data-lang=&quot;clike&quot;&gt;Robot ironMan = new Robot{ id=1, model=&amp;quot;T1000&amp;quot;, name=&amp;quot;arnold&amp;quot; }&lt;/pre&gt;
  &lt;p id=&quot;i9ZR&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;MjvX&quot;&gt;Статические поля и методы&lt;/h3&gt;
  &lt;p id=&quot;CtYB&quot;&gt;Помимо обычных полей и методов (членов экземпляра) классы могут иметь статические члены. Статические поля и методы не влияют на состояние экземпляров и не зависят от их жизненного цикла, а относятся к самому классу. Для указания статичности используется модификатор &lt;code&gt;static&lt;/code&gt;. &lt;br /&gt;Статические методы могут обращаться внутри своего класса только с статическим членам, а статические классы могут содержать только, соответственно, статические члены. &lt;/p&gt;
  &lt;pre id=&quot;7ZRu&quot; data-lang=&quot;clike&quot;&gt;class Robot
{
    public int id;
    public static string model = &amp;quot;T1000&amp;quot;;
    public static string name = &amp;quot;fuckhead&amp;quot;;
    
    public static string GetInfo()
    {
        return $&amp;quot;Model {model} is {name}&amp;quot;;
    }
    
    public void Dance()
    {
        Console.WriteLine($&amp;quot;I&amp;#x27;m {name} robot with id={id}, &amp;quot; +
            $&amp;quot;I will dance&amp;quot;);
    }
}&lt;/pre&gt;
  &lt;p id=&quot;YWwh&quot;&gt;Статические переменные инициализируются при первом обращении к классу. Для обращения к статическим членам используется имя класса, а не имена объектов.&lt;/p&gt;
  &lt;pre id=&quot;RJ5d&quot; data-lang=&quot;clike&quot;&gt;Robot.name = &amp;quot;Bender&amp;quot;;
Console.WriteLine(Robot.GetInfo()); //Model T1000 is Bender&lt;/pre&gt;
  &lt;p id=&quot;oIDE&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;oBDU&quot;&gt;По той причине, что константы должны быть проинициализированы при их объявлении и не могут быть в последствии изменены, то они, собственно, также не влияют на состояние объектов. А т.к. константы не относятся к объектам, то они относятся к их классу в целом, и обращение осуществляется также через имя класса.&lt;/p&gt;
  &lt;h3 id=&quot;Xll7&quot;&gt;&lt;br /&gt;Области видимости и спецификаторы доступа&lt;/h3&gt;
  &lt;p id=&quot;JEGT&quot;&gt;Все переменные существуют в рамках их области видимости, некоторого фрагмента кода, контекста переменной. Вне данной области переменная не будет существовать и к ней нельзя получить доступ.&lt;/p&gt;
  &lt;p id=&quot;UybZ&quot;&gt;Можно выделить следующие контексты:&lt;/p&gt;
  &lt;ul id=&quot;H8IB&quot;&gt;
    &lt;li id=&quot;UeHs&quot;&gt;Контекст класса - перменные-поля класса существуют в области видимости до тех пор, пока в этой области находится, содержащий их класс;&lt;/li&gt;
    &lt;li id=&quot;KRAP&quot;&gt;Контекст метода - переменные, определенные в методе, доступны только в рамках данного метода и являются локальными, они существуют с начала объявления и до конца тела метода. Значения аргументов метода, переданных при вызове метода, считаются локальными переменными метода, а значит они также существуют до конца тела метода.&lt;/li&gt;
    &lt;li id=&quot;BR2y&quot;&gt;Контекст блока кода - переменные, определенные на уровне блока кода, также являются локальными и доступны только в рамках данного блока и существуют до конца этого блока кода.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;PE8O&quot; data-lang=&quot;clike&quot;&gt;class Car
{
    public string model;
    
    public void Drive()
    {
        Console.WriteLine($&amp;quot;{model} say tr tr tr&amp;quot;);
    }
}

class Person
{
    public static string name = &amp;quot;Sanya&amp;quot;;

    public static void DriveOnCar()
    {
        /*Поле model объекта класса Car существует в рамках контекста Car
        *и будет существовать в методе DriveOnCar 
        *пока существует данный объект класса.
        */
        Car car = new Car(){ mdoel=&amp;quot;bmw&amp;quot; };
        car.Drive();
    }
    
    public static void Dance()
    {
        {
            /*Какой-то блок кода с локальной перменной a,
            *которая не будет доступна вне данного блока.
            *Т.е. вне этих скобочек в методе Dance мы уже
            *не сможем к ней обратиться.
            *Написал данное чисто для примера, 
            *не смог придумать ничего лучше.
            */
            int a = 1; 
        }
        
        /*От сюда мы не можем обратиться к a, ее здесь уже не существует)
        *И из данного метода мы не можем обратиться к объекту класса Car,
        *который создается в методе DriveOnCar, он существует в контексте 
        *только того метода.
        *
        *Но есть доступ к переменной name, которая находится 
        *в контексте класса Person.
        */
        
        Console.WriteLine($&amp;quot;{name} dick suck, and now dance&amp;quot;)
    }
}&lt;/pre&gt;
  &lt;p id=&quot;Meeu&quot;&gt;При работе с переменными надо учитывать, что локальные переменные, определенные в методе или в блоке кода, скрывают переменные уровня класса, если их имена совпадают. &lt;/p&gt;
  &lt;pre id=&quot;YfiN&quot; data-lang=&quot;clike&quot;&gt;class Robot
{
    public string name = &amp;quot;fuckhed&amp;quot;;
    
    public void Dance()
    {
        string name = &amp;quot;NewName&amp;quot;;
        Console.WriteLine($&amp;quot;I&amp;#x27;m {name} robot with id={id}, &amp;quot; +
                $&amp;quot;I will dance&amp;quot;);
    }
    
    public void OldDancer()
    {
        Console.WriteLine($&amp;quot;I&amp;#x27;m {name} robot with id={id}, &amp;quot; +
                $&amp;quot;I will dance&amp;quot;);
    }
}&lt;/pre&gt;
  &lt;p id=&quot;IxrC&quot;&gt;Все члены класса имеют спецификатор доступа, который определяет доступность компонента относительно другого кода в программе.&lt;/p&gt;
  &lt;p id=&quot;tHk2&quot;&gt;Существуют следующие спецификаторы:&lt;/p&gt;
  &lt;ul id=&quot;Fqbs&quot;&gt;
    &lt;li id=&quot;hrWq&quot;&gt;&lt;code&gt;public&lt;/code&gt; - элемент доступен из любого места программы;&lt;/li&gt;
    &lt;li id=&quot;M3UR&quot;&gt;private - доступен только внутри класса;&lt;/li&gt;
    &lt;li id=&quot;8WQr&quot;&gt;&lt;code&gt;protected&lt;/code&gt; - доступен в самом классе и в классах наследниках;&lt;/li&gt;
    &lt;li id=&quot;0AXt&quot;&gt;&lt;code&gt;internal&lt;/code&gt; - доступ возможен из любого кода в той же сборке; (Сборкой можно называть отдельно работающую программу (исполняемый файл exe) либо библиотеку(dll));&lt;/li&gt;
    &lt;li id=&quot;yq0T&quot;&gt;&lt;code&gt;private protected&lt;/code&gt; - доступ к члену класса возможен внутри класса и &lt;em&gt;&lt;strong&gt;в объявляющей сборке&lt;/strong&gt;&lt;/em&gt; для классов-наследников.&lt;/li&gt;
    &lt;li id=&quot;3lHx&quot;&gt;protected internal - доступ к члену класса возможен только из его объявляющей сборки и из классов-наследников &lt;em&gt;&lt;strong&gt;в любой сборке&lt;/strong&gt;&lt;/em&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;figure id=&quot;zx1p&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f2/7e/f27e5533-a2bd-4fa7-b113-114c9aab4575.png&quot; width=&quot;870&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;PUX0&quot;&gt;Если спецификатор не указать вручную, то будет присвоен спецификатор по умолчанию:&lt;/p&gt;
  &lt;ol id=&quot;DY7e&quot;&gt;
    &lt;li id=&quot;FpAj&quot;&gt;Для всех классов модификатором доступа по умолчанию является &lt;code&gt;internal&lt;/code&gt;;&lt;/li&gt;
    &lt;li id=&quot;8s8A&quot;&gt;Для всех элементов класса модификатором доступа по умолчанию является &lt;code&gt;private&lt;/code&gt;.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;LK3u&quot;&gt;Теперь должно быть понятно, зачем мы прописывали &lt;code&gt;public&lt;/code&gt; и зачем у метода Main указывался модификатор &lt;code&gt;static&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;2Feg&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;pJfB&quot;&gt;На этом пока все. В следующей статье разберем управляющие конструкции и далее будем углубляться в работу с классами, разбирая все более крутые фишки языка C#. Берегите себя, пока!)&lt;/p&gt;

</content></entry><entry><id>notserious_seesharp:first_program</id><link rel="alternate" type="text/html" href="https://teletype.in/@notserious_seesharp/first_program?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=notserious_seesharp"></link><title>Первая программа</title><published>2022-10-07T11:02:40.129Z</published><updated>2024-01-05T19:03:32.989Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/c6/d5/c6d5a77e-e0ce-4926-ac29-93f35cf7149f.png"></media:thumbnail><category term="osnovy-c" label="Основы C#"></category><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/91/c9/91c9a84e-fc90-49db-96ec-708823ad6d76.png&quot;&gt;Привет! В этой статье разберем правила построения программы на C#, структуру проекта, немного поговорим о переменных и основных операциях с ними, а также соберем и запустим первую программу. Летс го)</summary><content type="html">
  &lt;p id=&quot;vaq8&quot;&gt;Привет! В этой статье разберем правила построения программы на C#, структуру проекта, немного поговорим о переменных и основных операциях с ними, а также соберем и запустим первую программу. Летс го)&lt;/p&gt;
  &lt;figure id=&quot;pusH&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/91/c9/91c9a84e-fc90-49db-96ec-708823ad6d76.png&quot; width=&quot;900&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;v06g&quot;&gt;Понятие программы&lt;/h3&gt;
  &lt;p id=&quot;FvCi&quot;&gt;В самом начале обучения программированию на каком-либо языке многие сейчас в своих лекциях по какой-то причине не уделяют время таким базовым понятиям как &amp;quot;программа&amp;quot;, &amp;quot;алгоритм&amp;quot;, &amp;quot;язык программирования&amp;quot;. Я считаю, что такое начало неправильное, поэтому давайте ответим на эти основополагающие вопросы.&lt;/p&gt;
  &lt;p id=&quot;EE8t&quot;&gt;Дональд Кнут в первом тому своем труда &amp;quot;Искусство программирования&amp;quot; ооооочень подробно описал историю происхождения термина алгоритма и дал хорошее определение:&lt;/p&gt;
  &lt;blockquote id=&quot;Jv9Y&quot;&gt;&lt;em&gt;Алгоритм&lt;/em&gt; — это конечный набор правил, который определяет последовательность операций для решения конкретного множества задач и обладает пятью важными чертами: конечность, определённость, ввод, вывод, эффективность&lt;/blockquote&gt;
  &lt;p id=&quot;wi0r&quot;&gt;То есть, если совсем простыми словами, алгоритм - это описание некоторого порядка действий, выполнение которого позволяет получить требуемый результат за конечное число шагов.&lt;/p&gt;
  &lt;p id=&quot;enwx&quot;&gt;Программой же является совокупность операций (команд), написанных на языке программирования и направленных на работу с объектами (данными). Данный набор команд, написанных на языке программирования, может быть выполнен компьютером. &lt;/p&gt;
  &lt;p id=&quot;YycN&quot;&gt;Языком программирования называется формальный язык, предназначенный для записи алгоритмов из данных команд. Язык программирования определяется системой правил работы с заданным набором типов данных и имплементируется компилятором.&lt;/p&gt;
  &lt;p id=&quot;POyC&quot;&gt;Таким образом мы определили, что структура любой программы - это некоторый алгоритм из набора команд на формальном языке, предназначенный для работы с данными и выполняющийся компьютером)&lt;/p&gt;
  &lt;h3 id=&quot;lYvE&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;ctu4&quot;&gt;Базовые принципы построение программы на языке C#&lt;/h3&gt;
  &lt;p id=&quot;N16x&quot;&gt;Код каждой программы на языке C# должен отвечать 4-м основным принципам для его успешной компиляции и выполнения:&lt;/p&gt;
  &lt;ol id=&quot;lqOU&quot;&gt;
    &lt;li id=&quot;lmy0&quot;&gt;В С# каждая команда начинается с новой строки и заканчивается точкой с запятой;&lt;/li&gt;
    &lt;li id=&quot;5amK&quot;&gt;Команда не может существовать сама по себе. Совокупность команд и различных управляющих конструкций, реализующих определенную логику или функциональную возможность, называется методом.&lt;/li&gt;
    &lt;li id=&quot;3d8z&quot;&gt;Программы в С# состоят из классов. Класс - это некоторый шаблон, описывающий некоторую группу объектов(сущностей) с помощью набора переменных (данных) и методов для работы с ними. Каждый класс следует описывать в отдельном файле, имя которого следует называть именем класса.&lt;/li&gt;
    &lt;li id=&quot;vFJu&quot;&gt;Выполнение программы начинается с метода Main. Минимальная исполняемая программа в С# всегда имеет метод Main.&lt;br /&gt;&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h3 id=&quot;m1bB&quot;&gt;Первая программа&lt;/h3&gt;
  &lt;p id=&quot;va4L&quot;&gt;Используя данные принципы, давайте напишем первую минимальную программу и разберем на коде, что в ней присутствует, после того, как попробуем запустить.&lt;/p&gt;
  &lt;p id=&quot;5oSe&quot;&gt;&lt;em&gt;Сначала хочу показать, как это все делается, используя минимальные средства, далее установим и настроим IDE. Но перед началом необходимо установить .NET с &lt;a href=&quot;https://learn.microsoft.com/ru-ru/dotnet/core/install/windows?tabs=net60&quot; target=&quot;_blank&quot;&gt;сайта Microsoft&lt;/a&gt; для своей операционки, ничего сложного там нет качаете инсталятор и устанавлаиваете, тыкая &amp;quot;далее&amp;quot;, либо с помощью средств командной строки, там подробно все расписано.  &lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;a0vx&quot;&gt;Итак, для начала создаем отдельную папочку для проекта, в моем примере это &lt;code&gt;FirstProgram&lt;/code&gt;, открываем блокнот и пишем простейший класс &lt;code&gt;Robot&lt;/code&gt; с одним методом &lt;code&gt;Main&lt;/code&gt;, сохраняем файл класса с расширением &lt;code&gt;.cs&lt;/code&gt;.&lt;/p&gt;
  &lt;figure id=&quot;G4Kk&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/79/64/796427de-4c0d-439a-b49c-039d2ebee7a9.png&quot; width=&quot;538&quot; /&gt;
    &lt;figcaption&gt;А вы думали, я вам код текстом дам?! Нет, переписывайте, набивайте руку)))&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;bQPM&quot;&gt;Для сборки проекта .NET также необходим файл &lt;code&gt;.csproj&lt;/code&gt; с неким &amp;quot;описанием проекта&amp;quot;, как он будет собираться. Создаем его &lt;code&gt;FirstProgram.csproj&lt;/code&gt;)))&lt;/p&gt;
  &lt;pre id=&quot;jFVM&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;Project Sdk=&amp;quot;Microsoft.NET.Sdk&amp;quot;&amp;gt;
  &amp;lt;PropertyGroup&amp;gt;
    &amp;lt;OutputType&amp;gt;Exe&amp;lt;/OutputType&amp;gt;
    &amp;lt;TargetFramework&amp;gt;net6.0&amp;lt;/TargetFramework&amp;gt;
  &amp;lt;/PropertyGroup&amp;gt;
&amp;lt;/Project&amp;gt;&lt;/pre&gt;
  &lt;p id=&quot;7G3V&quot;&gt;В нем сейчас указан выходной тип сборки, исполняемый файл exe(т.к. у меня шиндовс), и  версия .NET 6.&lt;/p&gt;
  &lt;p id=&quot;VZY0&quot;&gt;Теперь открываем открываем папочку с нашим проектом и прописываем команду &lt;code&gt;dotnet build&lt;/code&gt; для сборки проекта, с помощью драйвера .NET.&lt;/p&gt;
  &lt;figure id=&quot;qs5K&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/1a/9f/1a9f7b1b-dfb0-42e7-ba6a-a90a8908e920.png&quot; width=&quot;857&quot; /&gt;
    &lt;figcaption&gt;Для запуска программы можно перейти в директорию &lt;code&gt;bin&lt;/code&gt;, в которой будет располагаться полученный исполняемый файл, либо воспользоваться командой &lt;code&gt;dotnet run&lt;/code&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;IqDh&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;QMMw&quot;&gt;Компоненты простейшей программы&lt;/h3&gt;
  &lt;p id=&quot;HPZP&quot;&gt;Теперь поговорим о том, что мы вообще написали.&lt;/p&gt;
  &lt;p id=&quot;Pcuc&quot;&gt;&lt;strong&gt;Пространство имен и директива using&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;sAG7&quot;&gt;Пространство имен &lt;code&gt;namespace&lt;/code&gt; является неким &amp;quot;контейнером&amp;quot; для классов, в котором все имена должны быть уникальными, таким образом оно позволяют избежать конфликта имен между классами. Проблема состоит в том, что при написании кода мы можем использовать библиотеки с кодом других программистов. И на стадии компоновки может возникнуть ситуация, когда и в вашем коде и в коде вашего коллеги используется одни и тоже имена классов, что приведет к неоднозначности. &lt;/p&gt;
  &lt;p id=&quot;jLIx&quot;&gt;В качестве примера можем взять проект из двух файлов Robot1.cs и Robot2.cs, при чем оба класса мы &amp;quot;нечаянно&amp;quot; назвали &lt;code&gt;Robot&lt;/code&gt;, а в методе &lt;code&gt;Main&lt;/code&gt; мы обращаемся к методу &lt;code&gt;Say()&lt;/code&gt; нашего робота. При компоновке возникнет, соответственно неоднозначность. Компилятор не сможет понять, какой именно класс мы хотим использовать.&lt;/p&gt;
  &lt;figure id=&quot;brLd&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/7d/0d/7d0d80ab-c826-434b-ba1a-6670a3746baa.png&quot; width=&quot;1040&quot; /&gt;
    &lt;figcaption&gt;Классы, которые не помещенны в какое-то собственное пространство имен, лежат в глобальном пространстве имен&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;bqhA&quot;&gt;Решить данную проблему можно либо изменив имя одного из классов, либо поместив их в собственные пространства имен, что предпочтительно.&lt;/p&gt;
  &lt;p id=&quot;QTPD&quot;&gt;Пространство имен определяется с помощью ключевого слова &lt;code&gt;namespace&lt;/code&gt; следующим образом:&lt;/p&gt;
  &lt;pre id=&quot;tCdE&quot;&gt;namespace Имя
{ 
    Определение типов 
}&lt;/pre&gt;
  &lt;figure id=&quot;NQLD&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/05/b6/05b6e7ee-802c-4e98-95f5-89486ab8ed66.png&quot; width=&quot;1100&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;fv0t&quot;&gt;Хорошим тоном для выбора пространства имен считается: &lt;code&gt;Компания.Проект.ИмяПрограммы&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;ot7S&quot;&gt;Для доступа к типам, объявленным внутри определенного пространства имен, нужно использовать оператор точку &lt;code&gt;.&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;xjnm&quot;&gt;&lt;code&gt;ИмяПространстваИмен.ИмяКласса.Метод&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;ks7b&quot;&gt;Полное имя класса не нужно прописывать, если выполняется одно из двух условий:&lt;/p&gt;
  &lt;ul id=&quot;COEs&quot;&gt;
    &lt;li id=&quot;sfeY&quot;&gt;Мы находимся в том же самом пространстве имен. То есть, если класс объявлен внутри фигурных скобок пространства имен и используется в них же;&lt;/li&gt;
    &lt;li id=&quot;RRlk&quot;&gt;Пространство имен подключено с помощью директивы &lt;code&gt;using&lt;/code&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;EcpF&quot;&gt;В первой программе, которую мы запускали, мы уже использовали &lt;code&gt;using&lt;/code&gt; для подключения стандартного пространства имен &lt;code&gt;System&lt;/code&gt;, в котором содержится класс &lt;code&gt;Console&lt;/code&gt;, с помощью которого можно обращаться к консоли.&lt;/p&gt;
  &lt;p id=&quot;h6rL&quot;&gt;Но все же иногда возникает потребность обратиться по полному имени к какому-то методу или классу, т.к. при подключении пространства имен через using может возникнуть неоднозначная ситуация, когда одно и то же имя есть в двух пространствах, как в примере выше. Тогда мы можем использовать еще одно назначение &lt;code&gt;using&lt;/code&gt; для создания псевдонима и обращаться в коде программы через него.&lt;/p&gt;
  &lt;figure id=&quot;H04d&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/21/38/2138b815-0124-463a-a733-ec8446e8ef16.png&quot; width=&quot;555&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;84rK&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;xqTM&quot;&gt;Переменные и их представление в памяти&lt;/h3&gt;
  &lt;p id=&quot;to7d&quot;&gt;Любая программа работает с данными, которые могут вводиться пользователем, вычисляться, подтягиваться из других ресурсов или прописываться в коде программы, эти данные надо где-то хранить. Постоянные данные хранятся на жестком диске, а временные данные — в оперативной памяти. Под временными данными я подразумеваю все, что необходимо программе для расчетов. Для того чтобы обработать данные, их необходимо сначала подгрузить в оперативную память и затем передать процессору, так как процессор не умеет самостоятельно хранить большое количество информации и не имеет прямого доступа к оперативной памяти. &lt;/p&gt;
  &lt;p id=&quot;xiwt&quot;&gt;Так вот, память можно себе представить как некоторую сетку с множеством ячеек с уникальными номерами, каждая из которых хранит (в основном) по 1 байту (8 бит) информации. После загрузки некоторых данных (например целого числа) в оперативную память, эти данные будут располагаться там последовательно, начиная с ячейки с неким адресом &lt;code&gt;n&lt;/code&gt;, а заканчивая ячейкой &lt;code&gt;n + size&lt;/code&gt;, где &lt;code&gt;size&lt;/code&gt; - размер данных.&lt;/p&gt;
  &lt;p id=&quot;43Yk&quot;&gt;Мы можем обращаться по адресу памяти, читать от туда данные и менять их, но, согласитесь, человеку было бы не удобно работать с такими адресами напрямую &lt;code&gt;0x7ffda41c5fac&lt;/code&gt;)) Для этого были придуманы переменные и константы.&lt;/p&gt;
  &lt;p id=&quot;vwLr&quot;&gt;Переменная - это именованная область памяти, заданного типа. Тип определяет, какого рода данные могут храниться в переменной, а обращаться к этой области памяти можно по имени.&lt;/p&gt;
  &lt;pre id=&quot;ORjR&quot;&gt;тип имяПеременной;&lt;/pre&gt;
  &lt;p id=&quot;tjEY&quot;&gt;Мы можем задать любое имя переменной, удовлетворяющее следующим правилам:&lt;/p&gt;
  &lt;ul id=&quot;724v&quot;&gt;
    &lt;li id=&quot;ziG9&quot;&gt;имя может содержать любые цифры, буквы и символ подчеркивания, при этом первый символ в имени должен быть буквой или символом подчеркивания;&lt;/li&gt;
    &lt;li id=&quot;mhag&quot;&gt;в имени не должно быть знаков пунктуации и пробелов;&lt;/li&gt;
    &lt;li id=&quot;RaO1&quot;&gt;имя не может быть ключевым словом языка C#.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;0Z0E&quot;&gt;Но при именовании переменной следует отражать ее назначение и по правилам кодстайла именовать, начиная с маленькой буквы (например так &lt;code&gt;arraySize&lt;/code&gt;)&lt;/p&gt;
  &lt;p id=&quot;Heup&quot;&gt;После определения переменной можно присвоить ей некоторое значение, обратиться к ней для получения значения и изменить ее значение:&lt;/p&gt;
  &lt;pre id=&quot;JHqI&quot; data-lang=&quot;clike&quot;&gt;int a = 5;
Console.WriteLine(a); //5
a = 4;
Console.WriteLine(a); //4&lt;/pre&gt;
  &lt;p id=&quot;BRWf&quot;&gt;Основной особенностью констант от переменных является то, что константы должны быть обязательно проинициализированы при определении, и после определения значение константы не может быть изменено. (есть еще некоторые особенности, но про них мы поговорим позднее:)&lt;/p&gt;
  &lt;pre id=&quot;oIqb&quot; data-lang=&quot;clike&quot;&gt;const int someSize = 10;
Console.WriteLine(someSize);&lt;/pre&gt;
  &lt;p id=&quot;jLj1&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;9XSM&quot;&gt;Типы данных&lt;/h3&gt;
  &lt;p id=&quot;FObG&quot;&gt;C# является строго типизированным языком. Каждая переменная и константа имеет тип, как и каждое выражение, результатом вычисления которого является значение. Каждое объявление метода задает имя, тип для каждого входного параметра и для возвращаемого значения. В библиотеке классов .NET определены встроенные числовые типы и комплексные типы, представляющие разнообразные конструкции.&lt;/p&gt;
  &lt;p id=&quot;HCaG&quot;&gt;Тип данных однозначно определяет:&lt;/p&gt;
  &lt;ul id=&quot;of1Z&quot;&gt;
    &lt;li id=&quot;DAVG&quot;&gt;внутреннее представление данных, а следовательно, и множество их возможных значений;&lt;/li&gt;
    &lt;li id=&quot;6JhV&quot;&gt;допустимые действия над данными (операции и методы).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;CT0A&quot;&gt;Все базовые типы можно условно разделить на целочисленные, числа с плавающей точкой, логические и строки.&lt;/p&gt;
  &lt;p id=&quot;iQFC&quot;&gt;&lt;strong&gt;Целочисленные типы&lt;/strong&gt;&lt;/p&gt;
  &lt;figure id=&quot;Dei4&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/05/6e/056e5dc3-18d2-48bc-a88b-ef6ff7e11b75.png&quot; width=&quot;758&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;lQ9V&quot;&gt;&lt;strong&gt;Числа с плавающей точкой&lt;/strong&gt;&lt;/p&gt;
  &lt;figure id=&quot;xhMZ&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e9/76/e976cd75-c810-4466-a5a4-6c16cf1ac6f5.png&quot; width=&quot;835&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ZSLn&quot;&gt;В C# реализовано большинство операций над числовыми типами, которые также присутствуют и в других языках:&lt;/p&gt;
  &lt;ul id=&quot;DbAq&quot;&gt;
    &lt;li id=&quot;H6V4&quot;&gt;Сложение +&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;acvj&quot; data-lang=&quot;clike&quot;&gt;int a = 10;
int b = a + 5; //15
double c = 10 + 15.156; //25.156&lt;/pre&gt;
  &lt;ul id=&quot;4Mfd&quot;&gt;
    &lt;li id=&quot;2lAl&quot;&gt;Вычитание -&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;CdZd&quot; data-lang=&quot;clike&quot;&gt;int a = 15;
int b = a - 5; //10&lt;/pre&gt;
  &lt;ul id=&quot;qa6o&quot;&gt;
    &lt;li id=&quot;2xB8&quot;&gt;Умножение *&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;qXpx&quot; data-lang=&quot;clike&quot;&gt;int a = 15;
int b = a * 5; //75&lt;/pre&gt;
  &lt;ul id=&quot;0fGx&quot;&gt;
    &lt;li id=&quot;992y&quot;&gt;Деление  /&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;NdDv&quot; data-lang=&quot;clike&quot;&gt;int a = 5 / 2; //2

double b = 5 / 2; //2

doubcle c = 5.0 / 2; //2.5

double d = 10;
double e = 3;
double g = d / e; //3.333333&lt;/pre&gt;
  &lt;p id=&quot;WyJR&quot;&gt;При делении стоит учитывать, что если оба операнда являются целыми числами, то результат также будет целым, то есть мы получим только целую часть от деления.&lt;/p&gt;
  &lt;ul id=&quot;Oy4u&quot;&gt;
    &lt;li id=&quot;y0rK&quot;&gt;Остаток от деления %&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;vZCK&quot; data-lang=&quot;clike&quot;&gt;double d = 10;
double e = 3;
double g = d % e; //1&lt;/pre&gt;
  &lt;ul id=&quot;oPIk&quot;&gt;
    &lt;li id=&quot;wZek&quot;&gt;Унарный минус&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;h5De&quot; data-lang=&quot;clike&quot;&gt;int a = 1;
a = -a; //-1&lt;/pre&gt;
  &lt;ul id=&quot;oqgE&quot;&gt;
    &lt;li id=&quot;bHHA&quot;&gt;Операции инкремента и декремента&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;1yyG&quot;&gt;Данные операции прибавляют и убавляют значение числа на 1, соответственно. Бывает префиксная и постфиксная формы. Префиксная форма инкремента&lt;code&gt;++x&lt;/code&gt; - сначала значение переменной x увеличивается на 1, а потом ее значение возвращается в качестве результата операции. Постфиксная форма инкремента &lt;code&gt;x++&lt;/code&gt; - сначала значение переменной x возвращается в качестве результата операции, а затем к нему прибавляется 1.&lt;/p&gt;
  &lt;pre id=&quot;hY16&quot; data-lang=&quot;clike&quot;&gt;int a = 5;
a++; // a=6
int b = a++; // a=7 b=6

int c = 8;
int d = ++c; // c=9 d=9

int n = 16;
n--; // 15
int t = --n; // t=14 n=14&lt;/pre&gt;
  &lt;ul id=&quot;2dzU&quot;&gt;
    &lt;li id=&quot;fosV&quot;&gt;Составные операции присваивания&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;wCD6&quot;&gt;Помимо простой операции присваивания &lt;code&gt;=&lt;/code&gt; в C# определены составные операции присваивания. Как и операция =, они все приводят к присваиванию значения той переменной, которая находится в их левой части, при выполнении с ней операции с выражением, находящимся в правой части.&lt;/p&gt;
  &lt;pre id=&quot;26ns&quot; data-lang=&quot;clike&quot;&gt;int a = 5;
int b = 6;
a += b; // a=11
a -= b - 4 // a=1&lt;/pre&gt;
  &lt;p id=&quot;lnag&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;mJYr&quot;&gt;&lt;strong&gt;Булевый тип&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;Y1Nr&quot;&gt;Данные тиа bool обычно используют для проверки тех или иных условий. Литералы булевского типа – это ключевые слова &lt;code&gt;true&lt;/code&gt; и &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;Dsnx&quot; data-lang=&quot;clike&quot;&gt;int a = 5;
int b = 5;
int c = 10;

bool d = a == b; //true
d = a == c; // false
d = a &amp;lt; c; // true &lt;/pre&gt;
  &lt;p id=&quot;yuYb&quot;&gt;&lt;em&gt;Важно отметить, что в языке C# не разрешено преобразование булевого типа в числовые значения, &lt;code&gt;true&lt;/code&gt; в &lt;code&gt;1&lt;/code&gt; и &lt;code&gt;false&lt;/code&gt; в &lt;code&gt;0&lt;/code&gt;, как это реализуется в других языках, например в C++.&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;t8K8&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;N0lc&quot;&gt;&lt;strong&gt;Символьный тип&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;qCYc&quot;&gt;Значение символьного типа может хранить одиночный символ либо числовой код в кодировке Unicode, заключенным в одинарные кавычки. &lt;br /&gt;Unicode – это набор символов, включающий символы латинского алфавита, символы национальных алфавитов, цифры, знаки препинания и специальные символы. Каждый символ набора Unicode имеет код из диапазона от 0 до 65535. Первые 128 символов Unicode совпадают с символами стандарной части таблицы ASCII.&lt;/p&gt;
  &lt;pre id=&quot;nvx2&quot; data-lang=&quot;clike&quot;&gt;char a = &amp;#x27;a&amp;#x27;;
char b = &amp;#x27;\u0420&amp;#x27;;&lt;/pre&gt;
  &lt;p id=&quot;Q5FN&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;ZG0Z&quot;&gt;&lt;strong&gt;String&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;H6Ak&quot;&gt;Тип string является структурой данных, хранящей набор символов типа char. Этот тип немного отличается от всех остальных, мы о нем еще успеем много поговорить)&lt;br /&gt;Строки указываются в двойных кавычках и обладают рядом свойств. Для сложения, конкатенации, строк можно использовать оператор &lt;code&gt;+&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;oxTW&quot; data-lang=&quot;clike&quot;&gt;string h = &amp;quot;Hallo&amp;quot;;
string w = &amp;quot;World&amp;quot; + h; // &amp;quot;Hallo World&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;lvzT&quot;&gt;В строках можно указывать управляющие последовательности, например &lt;code&gt;\n \t&lt;/code&gt; .&lt;/p&gt;
  &lt;pre id=&quot;fZRZ&quot; data-lang=&quot;clike&quot;&gt;string a = &amp;quot;Hallo\nWorld&amp;quot;;
/*
*Hallo
*World
*/&lt;/pre&gt;
  &lt;p id=&quot;Y8VS&quot;&gt;Для того, чтобы управляющие символы сделать частью строки, то есть чтобы в строке можно было прописать кавычки &lt;code&gt;&amp;quot;&lt;/code&gt; или &lt;code&gt;\&lt;/code&gt; можно применить экранирование, с помощью добавления к ним символа обратного слэша либо отказаться от использования в строках управляющей последовательности, с помощью символа @ перед строкой.&lt;/p&gt;
  &lt;pre id=&quot;Kzc2&quot; data-lang=&quot;clike&quot;&gt;string path = @&amp;quot;C:\Users\artem\notSeriousSeeSharp\FirstProgram&amp;quot;

string name = &amp;quot;\&amp;quot;cool\&amp;quot; story&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;wkNj&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;QTSs&quot;&gt;Функция интерполяции строк создана на основе функции составного форматирования и имеет более удобный синтаксис для включения форматированных результатов выражения в строку результатов.&lt;/p&gt;
  &lt;p id=&quot;kpKS&quot;&gt;Если по простому, то это штука, которая позволяет удобно форматировать строки и подставлять внутрь переменные и вычисляемые значения, которые в рантайме будут вычисляться и подставляться в строку)&lt;/p&gt;
  &lt;p id=&quot;z7J1&quot;&gt;Для определения строкового литерала в качестве интерполированной строки используется символ &lt;code&gt;$&lt;/code&gt; в начале строки.&lt;/p&gt;
  &lt;figure id=&quot;axKX&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/cb/15/cb151b21-d19e-4d31-8215-ffe315ef0b01.png&quot; width=&quot;872&quot; /&gt;
    &lt;figcaption&gt;Тут мы подставляем переменные name и years в последнюю строкку&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Dw62&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;trj0&quot;&gt;Установка IDE&lt;/h3&gt;
  &lt;p id=&quot;I5MH&quot;&gt;До этого момента было продемонстрировано, как компилить и запускать код с помощью консоли. Умение писать в блокноте - это конечно здорово и хороший навык, но без подсветки синтаксиса, удобной структуры проекта под рукой, полезным хоткеям и много другого, писать код можно, но это все же медленнее.&lt;/p&gt;
  &lt;p id=&quot;n3fO&quot;&gt;В связи с этим стоит использовать IDE (Integrated development environment) интегрированную среду разработки, которая бы включала в себя набор средств для автоматизации задачи сборки и компиляции кода, подсвечивала бы синтаксис, показывала бы возможные ошибки и предупреждения и прочие ништяки, ускоряющие процесс разработки.&lt;/p&gt;
  &lt;p id=&quot;cfs0&quot;&gt;Для написания кода на C# я использую Visual Studio от Microsoft и всем советую. Еще есть довольно популярная среда ReSharper от JetBrains, но лично мне не зашло и многие вещи в нем казались избыточными и отвлекающими, а подсказки от Intelsense в Visual Studio 2022 вполне сопоставимы с тем, что предлагает Rider в ReSharper. Поэтому покажу, как установить на Windows мою любимую визуалочку🥰 и создать первый проект.&lt;/p&gt;
  &lt;p id=&quot;mAno&quot;&gt;Переходим на официальный &lt;a href=&quot;https://visualstudio.microsoft.com/ru/downloads/&quot; target=&quot;_blank&quot;&gt;сайт макрософт&lt;/a&gt; и качаем инсталятор с версией Community Edition.&lt;/p&gt;
  &lt;figure id=&quot;SOBj&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/dc/c0/dcc0cfa9-8f01-4670-990e-525f7c16a98d.png&quot; width=&quot;1274&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;bo4S&quot;&gt;После загрузки инсталятора откроется окно с огромным количеством разных модулей, вам сейчас для одного шарпа необходим минимум из платформы .NET. Все остальное можно будет при надобности через этот же самый инсталятор впоследствии доустановить. Во вкладке &amp;quot;рабочие нагрузки&amp;quot; выбираете &amp;quot;разработка классических приложений .NET&amp;quot;, все нужные пакеты там будут, и этого будет достаточно, чтобы писать консольные приложения и формошлепить десктопные приложения.&lt;/p&gt;
  &lt;figure id=&quot;2QiN&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/db/da/dbdae996-dc1a-4b17-a78b-fce694f57b48.png&quot; width=&quot;1235&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;hUVm&quot;&gt;Жмякаете по кнопке установить в нижнем правом углу и у вас начнется загрузка. После установки, при запуске Visual Studio откроется окно для выбора открытия проекта или его создания. Создаем!&lt;/p&gt;
  &lt;figure id=&quot;BpWs&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d6/3e/d63e0293-62cd-46d6-8dcf-609867db55de.png&quot; width=&quot;886&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;o45h&quot;&gt;Откроется окно для выбора типа проекта с возможностью отфильтровать ненужное. Выставляем С# и консольное приложение, останется два типа проекта. Помним, что .Net Framework ныне не поддерживается и оно тянется легасем, и там старая версия язык C# 8, что нам не нравится и поэтому выбираем модное .NET Core приложение.&lt;/p&gt;
  &lt;figure id=&quot;ERcN&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f6/56/f6563e98-1f5f-45fe-b435-cce7ab695733.png&quot; width=&quot;883&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;WRjS&quot;&gt;Прописываем название проекта и каталог.&lt;/p&gt;
  &lt;figure id=&quot;B7zf&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ab/f4/abf4febc-f1c2-46d6-ad0e-e902064a1028.png&quot; width=&quot;879&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;duXu&quot;&gt;Выбираем версию .NET, которая будет использована в проекте.&lt;/p&gt;
  &lt;figure id=&quot;98vN&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/30/32/30322248-2367-4ca5-a0af-19b918d220c6.png&quot; width=&quot;876&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;xDiD&quot;&gt;После этого Visual Studio создаст и откроет для нас проект&lt;/p&gt;
  &lt;figure id=&quot;uss1&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ef/74/ef74862e-83fa-4974-ac3b-e1394e42945e.png&quot; width=&quot;1054&quot; /&gt;
    &lt;figcaption&gt;Слева находится &amp;quot;обозреватель решений&amp;quot;, в котором находится структура проекта, через который мы можем добавить новые его составляющие, клацнув пкм по имени проекта. Для запуска проекта можно воспользоваться комбинацией клавиш &lt;code&gt;ctrl + f5&lt;/code&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;dGpQ&quot;&gt;&lt;em&gt;И у вас наверняка сразу возникает вопрос, а что за фигня, где все то, про что ты нам втирал, где там эти намспайсы, классы методы?! Вон же, просто вывод в консоль, все запускается и работает! Это что получается, ты нас обманывал?!&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;Iwmt&quot;&gt;На самом деле, нет) Для .NET 5 и более ранних версий шаблон приложения должен обязательно выглядеть как показывал я, с классом и точкой входа, функцией Main. А начиная с версии .NET 6 с C# 10, оба варианта допустимы. &lt;/p&gt;
  &lt;p id=&quot;3Q6K&quot;&gt;При использовании .NET 6 необходимо только написать код метода &lt;code&gt;Main&lt;/code&gt;, а компилятор самостоятельно сгенерирует класс &lt;code&gt;Program&lt;/code&gt; с методом &lt;code&gt;Main&lt;/code&gt; и поместит все команды верхнего уровня из &lt;code&gt;Program.cs&lt;/code&gt; в этот &lt;code&gt;Main&lt;/code&gt; метод. То есть все, что было сказано про обязательное наличие точки входа, метода &lt;code&gt;Main&lt;/code&gt;, остается в силе)&lt;/p&gt;
  &lt;p id=&quot;Cre0&quot;&gt;&lt;em&gt;В нашем файле &lt;code&gt;Program.cs&lt;/code&gt; мы по прежнему можем создавать другие методы, их компилятор поместит в сгенерированный класс &lt;code&gt;Program&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;GceB&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;cagV&quot;&gt;Вроде бы это все, что я планировал освятить в данной статье. Возможно что-то получилось скомкано и что-то не будет понятно, и эти моменты нужно править. Поэтому оставляйте фидбек в комментах, если что-то непонятно, отвечу на вопросы, поправлю материал.&lt;/p&gt;
  &lt;p id=&quot;Y5W8&quot;&gt;В следующей статье поговорим об основе устройства классов и работе с ними, а также об основе устройства памяти в C#.&lt;/p&gt;
  &lt;p id=&quot;gyZY&quot;&gt;Подписывайтесь на наш несерьезный &lt;a href=&quot;https://t.me/serious_seesharp&quot; target=&quot;_blank&quot;&gt;тг-канал&lt;/a&gt;, ставьте ваши импортозамещенные славные классы)&lt;/p&gt;

</content></entry><entry><id>notserious_seesharp:Introduction</id><link rel="alternate" type="text/html" href="https://teletype.in/@notserious_seesharp/Introduction?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=notserious_seesharp"></link><title>Введение</title><published>2022-10-04T02:02:42.732Z</published><updated>2024-01-05T19:02:49.402Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/81/ae/81aeb98c-7a12-4826-9b6d-99298234e980.png"></media:thumbnail><category term="osnovy-c" label="Основы C#"></category><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/d0/fd/d0fd23ec-b0cd-4dd2-9da7-47fe4cdf7faf.png&quot;&gt;Перед началом работы хочу познакомить вас с понятием высокоуровневых языков программирования и немного погрузиться в историю появления языка C#, с чего все начиналось.</summary><content type="html">
  &lt;figure id=&quot;gZLQ&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d0/fd/d0fd23ec-b0cd-4dd2-9da7-47fe4cdf7faf.png&quot; width=&quot;900&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;i1r3&quot;&gt;Перед началом работы хочу познакомить вас с понятием высокоуровневых языков программирования и немного погрузиться в историю появления языка C#, с чего все начиналось.&lt;/p&gt;
  &lt;p id=&quot;AUyg&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;KC6h&quot;&gt;Машинный код, ассемблер&lt;/h3&gt;
  &lt;p id=&quot;Zud5&quot;&gt;Каждый компьютер работает только со своим набором команд, машинным языком, определяемым его аппаратной архитектурой. Машинные языки обычно состоят из чисел, которые в конечном итоге представляются в виде последовательности цифр: 0 и 1. Люди привыкли обозначать выполняемые операции знаками и словами, поэтому числовые версии данных операций в машинном языке стали называться кодом. В наше время понятие программного кода используется в более широком смысле, сейчас им именуются программные инструкции в языках любого уровня хоть в высокоуровневых языках и используются обозначений команд знаки и слова.&lt;/p&gt;
  &lt;p id=&quot;tYwi&quot;&gt;Понятное дело, работать на машинном языке неудобно, разработка велась медленно, и никакой фронтендер бы не согласился писать на машинном коде ни за какие деньги:) &lt;/p&gt;
  &lt;figure id=&quot;IJqk&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/44/61/446170f1-3631-478b-8e7a-796d29b8519c.png&quot; width=&quot;496&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;xlaH&quot;&gt;В связи с этим программисты стали обозначать элементарные операции сокращениями английских слов. Эти сокращения заложили основу ассемблерных языков. Программы трансляторы, называемые ассемблерами, быстро переводили код на ассемблерном языке в машинный код. Хотя код на ассемблерном языке лучше читается человеком, он остается непонятным компьютеру до преобразования в машинный код. Ассемблерные языки являются низкоуровневыми и, как правило, специфичны для конкретной архитектуры, поскольку позволяют программисту пользоваться текстовыми мнемониками машинных инструкций, а каждая такая мнемоника однозначно соответствует одной машинной машинной команде, выполняемой определенным процессором.&lt;/p&gt;
  &lt;p id=&quot;YQYa&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;uTzO&quot;&gt;Высокоуровневые языки&lt;/h3&gt;
  &lt;p id=&quot;IIql&quot;&gt;Для ускорения процесса программирования были разработаны языки высокого уровня. Они имитируют естественные языки, а одно выражение на языке высокого уровня может соответствовать набору различных инструкций машинного кода. Данные языки используют некоторые слова разговорного языка и общепринятые математические конструкции, поэтому более удобны человеку. &lt;/p&gt;
  &lt;p id=&quot;CqrW&quot;&gt;Языки программирования высокого уровня напрямую не зависят аппаратных средств, но требуют для работы наличия соответствующих программ-трансляторов, занимающихся преобразованием программы в язык машины, на которой данная программа будет выполняться. Данные программы-трансляторы называются компиляторами, а процесс преобразования - компиляцией (если очень упрощенно:).&lt;/p&gt;
  &lt;p id=&quot;2gfw&quot;&gt;Интерпретация конструкций языка программирования должна быть абсолютно однозначной, так как преобразование выражения программного кода в машинный код однозначно определяется программой-транслятором, и любой намек на неоднозначность либо делает эту фразу непереводимой, либо приводит к ошибке. (a++ не тоже самое, что ++a)&lt;/p&gt;
  &lt;p id=&quot;L7FS&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;McYP&quot;&gt;C# и .NET&lt;/h3&gt;
  &lt;p id=&quot;PcOb&quot;&gt;В 2000 году компания Microsoft представила общественности язык программирования C#  вместе с концепцией платформы .NET - концепция использования Интернета и веб-технологий в разработке, производстве, распространении и использовании программных продуктов. .NET по сути стал являться ультимативной платформой с большим разнообразием технологий для разработки различного рода приложений. .NET также позволяет создавать приложения на любом .NЕТ-совместимом языке (таком, как С#, Visual Basic, Visual С++).&lt;/p&gt;
  &lt;p id=&quot;Bop8&quot;&gt;Основой для исполнения программ платформы .NET является виртуальная машина CLR (Common Language Runtime). При компиляции код программы компилируется в сборку на промежуточном языке CIL (Common Intermediate Language), после чего подается для исполнения в CLR, а далее при запуске на выполнение подобного приложения происходит JIT-компиляция (Just-In-Time) в машинный код, который затем выполняется. Такое устройство .NET позволяет обеспечить перенос программы с одной платформы на другую при наличии на них данной виртуальной машины. &lt;/p&gt;
  &lt;p id=&quot;62rC&quot;&gt;Также благодаря данной трансляции .NET не привязывает программистов к какому-то конкретному языку, а позволяет разрабатывать приложения на любом языке, совместимым с .NET (C#, VB.NET, Delphi.NET, F#). .NET содержит огромную общую для всех поддерживаемых языков библиотеку классов, которая используется в также предоставляемых ей ряде технологий, разрабатываемых Microsoft. Данные технологии предоставляют разработчикам-пользователям api-интерфейсы для работы с десктопными приложениями (WPF, UWP), веб-приложениями (ASP.NET), для разработки мобильных и кроссплатформенных решений (Xamarin, MAUI), для работы с базами данных (Entity Framework)...&lt;/p&gt;
  &lt;p id=&quot;HVT8&quot;&gt;Microsoft долгое время развивало платформу .NET исключительно под Windows без всякой кроссплатформенности под названием .NET Framework. В 2019 была выпущена последняя версия этой платформы - .NET Framework 4.8, и ее поддержка ныне прекращена. Все это потому, что в 2014-м году они начали развивать альтернативную платформу .NET Core, ориентированную уже на кроссплатформенность (ничего нового... Microsoft:). Последовательно были выпущены версии .NET Core 1, .NET Core 2, .NET Core 3, .NET 5. Последней текущей версией платформы является .NET 6 с версией языка C# 10.&lt;/p&gt;
  &lt;p id=&quot;fO8m&quot;&gt;Таким образом у Microsoft параллельно развивалось две версии платформы .Net Framework и .Net Core. При этом на .Net Framework у многих уже успело накопиться в проектах довольно жирный кусок легаси, который надо как-то перетаскивать на .Net Core, но платформы были сделаны несовместимыми друг с другом:) По этой причине Microsoft пришлось разработать .Net Standart - это интерфейс/спецификация всего того, что реализуют все платформы .Net, благодаря чему можно подключить библиотеку с .Net Framework к Core.&lt;/p&gt;
  &lt;p id=&quot;lWXD&quot;&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;ZBuc&quot;&gt;Вроде бы рассказал все основные моменты касательно того, что из себя в общих чертах представляет .NET и C#. Полную историю языка можете почитать на &lt;a href=&quot;https://learn.microsoft.com/ru-ru/dotnet/csharp/whats-new/csharp-version-history&quot; target=&quot;_blank&quot;&gt;msdn&lt;/a&gt;. Ожидаем релиза .NET 7 c C# 11. В следующей статье распишу все про среду разработки, напишем первую программу и расскажу об основных ее компонентах. Подписывайтесь на наш &lt;a href=&quot;https://t.me/serious_seesharp&quot; target=&quot;_blank&quot;&gt;телеграм-канал&lt;/a&gt;, а меня ждут санитары, всем пока!)&lt;/p&gt;

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