← Academy Blog

Марафон підготовки 2022

Translated into:

Стартував марафон підготовки до тестів - .NET трек - Binary Studio Academy 2022! Щотижневі питання з відповідями та пояснення чекають на нашому блозі.

  • [.NET] Який тип має повертати даний метод?
    public static {ReturnType} GetNums()
    {
        var number = 0;
        while (true)
        {
            if (number > 5)
                yield break;
            yield return number++;
        }
    }

    a) void
    b) IEnumerable<int>
    c) List<int>
    d) int
    e) object

    Розгорнути правильну відповідь з поясненням

    Наведений метод використовує два спеціальних ключових слова yield break та yield return. Метод, у якому використовуються такі конструкції, називається ітератором. Ітератор може бути іменованим (як у нашому випадку), так і не іменованим. Не іменований ітератор оголошується для класу за допомогою визначення методу GetEnumerator(). У цьому випадку єкземпляри такого класу можна напряму використовувати у foreach чи для linq операцій. Відмінність ітератора від звичайного метода полягає у тому, що метод повертає єдине значення за допомогою return, а ітератор – декілька (або взагалі жодного). Ітератор повернить стільки значень, скільки викликано yield return. Виклик yield return можно написати декілька разів поспіль або, як у завданні, використати цикл. Ключове слово yield break означає що більше елементів повертати не потрібно, навіть якщо далі слідують іще конструкції yield return. Зупинка ітератора з використанням yield break не є обов'язковою. Наприклад ми можемо переписати код із завдання так:

    while (number < 5)
    {
        yield return number++;
    }

    Також цікавим є перехоплення помилок у ітераторі. Ключове слово yield не можна використовувати всередині try-catch блоку. Натомість потрібно спочатку отримати значення для повернення, а вже потім викликати yield наприклад:

    foreach (var item in data)
    {
      try
      {
          result = PrepareData(item);
      }
      catch (Exception ex)
      {
          Console.Error.WriteLine(ex.Message);
          continue;
      }
      yield return result;
    }

    У попередньому абзаці вже згадувалося де можна використати результат, шо повертає ітератор – у конструкції foreach та для linq операцій. А отже значення, що повертає ітератор, має бути інтерфейсом IEnumerable. Типізація ж для IEnumerable має відповідати саме тому типу, що повертає yield return. У нашому випадку це IEnumerable<int>. Правильна відповідь: a) IEnumerable

  • [.NET] Виконання якого рядку призведе до помилки?
    namespace BSA2021
    {
      class Program
      {
        static void Main()
        {
          var array = new string[] { "Welcome", "to", "Binary", "Studio", "Academy" };
    
          var a = array[..0];
          var b = array[2..^0];
          var c = array[^0];
        }
      }
    }
    

    a) 9
    b) 10
    c) 11
    d) Помилок при виконанні не буде

    Розгорнути правильну відповідь з поясненням

    У завданні наведено невеличку програма, яка складається із масиву строк, а також із дій над цим масивом. Дії над масивом виконуються з використанням індексів та діапазонів. Розглянемо їх детальніше:

    Індекс вказує на порядкове місце деякого елемента у послідовності. Індекс можна вказувати або рахуючи від початку послідовності, або від її кінця. У першому випадку індекс першого елемента є 0, другого – 1, третього – 2, і так далі. Останній елемента має індекс array.Length - 1. Для відрахунку елемента від кінця послідовності використовується спеціальний оператор ^. Останній елемент матиме індекс ^1, передостанній – ^2, а перший – ^array.Length. Індекс, записаний як ^0, відповідатиме довжині масиву.

    Діапазон задає деяку частину послідовності, яка обмежена двома індексами. Для визначення діапазону призначено оператор .. . Індекс початку діапазону вказується лівіше оператора, а кінця – правіше. Елемент, із індексом що відповідає початку діапазона, включається у діапазон. А елемент із індексом кінця – ні. У діапазоні можно не вказувати індекс початку і кінця, тоді початковим індексом буде вважатися 0, а кінцевим ^0.

    Тепер розглянемо хід виконання програми. На сьомому рядку корректно створюється масив строк із п'яти елементів. На 9-му рядку беремо діапазон, який починається із першого елемента і закінчується цим же першим елементом, тобто змінна a буде містити порожній масив строк. На 10-му рядку беремо діапазон із третього до останнього елемента, тобто змінна b містит {"Binary", "Studio", "Academy" }. 11-й рядок має присвоєння змінній єдиного елемента, який знаходиться на позиції ^0, тобто на array.Length. Тут маємо помилку виконання IndexOutOfRangeException.

    Правильна відповідь: c) 11

  • [.NET] IDOR prevention tool (не бійтеся назви, сама задача весела)Weekly Challenge

    Вступ

    У кібербезпеці існує вразливість, яка називається insecure direct object reference (IDOR). Простіше кажучи, це коли у користувача є можливість отримати доступ до даних, до яких він насправді не має доступу. Зазвичай це відбувається, коли програма показує ідентифікатори внутрішніх об’єктів у простій формі. Наприклад, https://medium.com/@elon/posts/82048f77 або навіть https://medium.com/@elon/posts/1.

    Уявімо, що це ваша публікація за цим посиланням. Якщо ви достатньо допитливі, ви, ймовірно, заміните деякі значення в GUID або підставите «2» замість «1», щоб побачити, що станеться.

    З іншого боку, може бути такий ендпоінт: https://medium.com/@elon/posts/PII6xnIiNeyv. У цьому випадку ви не знаєте, яке значення використовувати замість цього довгого ідентифікатора, щоб попитати щастя.

    Але як програміст ви можете припустити, що це не просто випадкові літери та цифри. І будете праві. Насправді, це зашифроване значення GUID.

    Завдання

    Створіть просту програму (Web API), щоб інтегрувати це шифрування/дешифрування. Використовуйте такі класи:

    Модель:

    public class Resource
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
    }

    Сервіс:

    public class ResourceService
    {
        private static readonly List<Resource> Resources = new();
    
        public Guid Add(string name)
        {
            var resource = new Resource
            {
                Id = Guid.NewGuid(),
                Name = name
            };
    
            Resources.Add(resource);
            return resource.Id;
        }
    
        public Resource Get(Guid id)
        {
            var resource = Resources.FirstOrDefault(x => x.Id == id);
            return resource;
        }
    }

    Контролер:

    [Route("api/[controller]")]
    [ApiController]
    public class ResourcesController : ControllerBase
    {
        private ResourceService _resourceService;
    
        public ResourcesController()
        {
            _resourceService = new ResourceService();
        }
    
        [HttpGet("{id}")]
        public Resource Get(Guid id)
        {
            return _resourceService.Get(id);
        }
    
        [HttpPost]
        public Guid Post([FromBody] string name)
        {
            return _resourceService.Add(name);
        }
    }

    Якщо ви створите просту програму ASP.NET Core Web API і використаєте ці класи, у вас буде 2 ендпоінти для додавання та отримання ресурсу. Основна загвоздка полягає в тому, що вам потрібно оновити код, щоб відповідати наступним вимогам:

    • Коли ви створюєте новий ресурс, вам потрібно отримувати ідентифікатор цього щойно створеного ресурсу в зашифрованому вигляді (не як GUID):
      • POST-запит: /api/resource з тілом: My new resource
      • Відповідь: PII6xnIiNeyv-Vxy
    • Коли ви отримуєте ресурс за його ідентифікатором, вам потрібно передавати його в зашифрованому вигляді
      • GET-запит: /api/resource/PII6xnIiNeyv-Vxy
      • Відповідь:
    {
      "id": "82048f77-9995-4287-bc0d-471ca3c394b3",
      "name": "My new resource"
    }

    Існує кілька способів виконати це завдання:

    1. Найпростіший та очевидний (шифрування та дешифрування в кожному контролері)
    2. Використання ASP.NET Core middleware
    3. Використання ASP.NET Core filters
    4. Custom JSON converters
    5. Напевно є й інші

    Деякі з них можуть здатися складними, тому намагайтеся рухатися один за іншим. Це гарна нагода дізнатися багато нового про корисні особливості ASP.NET Core.

    Можете використовувати будь-який вид шифрування.

    Розгорнути правильну відповідь з поясненням
  • [.NET] Яким буде результат виконання цього коду?
    class Program
    {
        static void Main(string[] args)
        {
            First first = new Second("some item");
            First another = new Third();
        }
    }
    class First
    {
        public First()
        {
            Console.WriteLine("First created");
        }
    }
    class Second : First
    {
        private Second()
        {
            Console.WriteLine("Second created");
        }
        public Second(string item)
        {
            Console.WriteLine("Second created with Item: {0}",  item);
        }
    }
    class Third : Second
    {
        public Third()
        {
            Console.WriteLine("Third created");
        }
    }

    a) First createdSecond created with Item: some itemFirst createdThird created
    b) Виникне помилка компіляції
    c) Second created with Item: some itemThird created
    d) Виникне помилка виконання

    Розгорнути правильну відповідь з поясненням

    Наведенні у завданні програма побудована із трьох класів та із виклику конструктора цих класів у Main(). Для класів використовується механізм успадкування і побудовано наступну ієрархію: від базового класу First успадковується клас Second в якого, в свою чергу, є нащадок – клас Third. Коли ми створюємо екземпляр класу, у якого є батьківський клас, спочатку викликається конструктор батьківського класу. За замовчуванням для батьківського класу буде викликано конструктор без параметрів. Для явного виклику потрібного конструктора призначено ключове слово base().

    Клас First має єдиний конструктор без параметрів, який виводить у консоль строку "First created".

    Клас Second має два конструктора. Один із них без параметрів і виводить строку "Second created", а також його рівень доступу визначено як private, а отже він доступний для виклику лише “всередині” цього класу. Інший конструктор має параметр item та друкує повідомлення "Second created with Item: {0}". Обидва ці конструктора неявно викликають конструктор First().

    Клас Third має єдиний конструктор без параметрів, який друкує "Third created" в консолі. Але, оскільки відсутній явний виклик батіківського констуктора з параметрамами, буде здійснено виклик приватного констуктора Second(). Це призведе до помилки під час компіляції CS0122.

    Правильна відповідь: b) Виникне помилка компіляції

  • [.NET] Створити Web API додаток із статичною in-memory базою даних (тобто ви маєте один екземпляр бази на весь додаток) розміром у 20 елементів максимум. Елемент повинен мати наступні поля - Id (унікальне), Name та CreationDate. Всі інші поля на ваш розсуд.Weekly Challenge

    Додаток має надавати доступ до наступних можливостей:

    1. Оновлення елемента у базі за Id.
    2. Додавання елемента у базу. Треба враховувати, що максимальний розмір списку - 20 елементів.
    3. Отримання нової структури даних яка складається з двох списків і поля з датою. Структура будується наступним чином: користувач передає дату на ендпоінт і по цій даті ми розбиваємо наш список на два - елементи, створені до вказаної дати і після. У полі з датою повертаємо передану дату.
    4. Видалення елемента зі списку за Id.
    Розгорнути правильну відповідь з поясненням
  • [.NET] Що виведе цей код?
    namespace BSA2022
    {
        struct Point
        {
            public int X;
            public int Y;
        }
    
        class Program
        {
            static void Main()
            {
                CreatePoint(out Point point);
                ResetPoint(in point);
                IncreasePoint(ref point);
    
                Console.WriteLine($"({point.X};{point.Y})");
            }
    
            static void ResetPoint(in Point point)
            {
                point.X = 1;
                point.Y = 1;
            }
    
            static void CreatePoint(out Point point)
            {
                point.X = 2;
                point.Y = 2;
            }
    
            static void IncreasePoint(ref Point point)
            {
                point.X *= 3;
                point.Y *= 3;
            }
        }
    }

    a) (3;3)
    b) (6;6)
    c) (0;0)
    d) Буде помилка при виконанні
    e) Буде помилка при компіляції

    Розгорнути правильну відповідь з поясненням

    Розглянемо із чого “складається” програма наведена у питанні. Вона побудована з простої структури та визові трьох методів для виконання дій над цією структурою. Складність полягає у тому, що структура, подібно до int, зберігається у стеці та є типом-значення. А також що у методах дій над структурою використовуються ключові слова (in, ref, out) що слугують модифікаторами параметрів. Без використання цих модифікаторів до метода завжди передається нова копія даних змінної, які зберігаються у стеці. Розглянемо детально ці модифікатори:

    Ключове слово ref вказує на те, що аргумент передається за посиланням, а не за значенням. Тобто будь-яка зміна значення цього аргументу всередині метода призведе до зміни відповідного аргумента з яким було викликано метод. Також використання цього ключового слова вимагає того, щоб змінна була ініціалізована перед її використанням як параметр, інакше буде помилка компіляції CS0165.

    Ключове слово out також вказує що аргумент передається за посиланням. Такий аргумент називається “вихідним параметром” метода і, на відміну від ref, не вимагає попередньої ініціалізації параметра. Натомість всередині метода аргумент має бути обов'язково ініціалізовано всередині цього методу, інакше виникає помилка компіляції CS0177.

    In – визначає параметр який передається за посиланням, однак всередині метода його заборонено змінювати. Таке ключове слово зазвичай використовується при необхідності передачі великих за обсягом пам’яті структур всередину метода. При спробі змінити такий параметр виникає помилка компіляції CS8332 – неможливість змінити readonly змінну. Також зберігається необхідність попередньої ініціалізації змінної, що передається як параметр.

    А тепер проаналізуємо виклик кожного методу із Main(). Першим йде виклик CreatePoint(). Його параметр позначено як out, а отже накладається умова на ініціалізацію всередині методу. Структура вважається ініціалізовано коли ініціалізовано всі її поля – отже помилок немає, і point має значення (2;2). Наступним йде виклик ResetPoint() із параметром in. Всередині метода виконується присвоєння цьому параметру, а отже маємо помилку компіляції CS0165.

    Правильна відповідь: e) помилка компіляції .

  • [.NET] КалькуляторWeekly Challenge

    Створіть калькулятор, який можна використовувати так:

    var result = 5.Add(10).MultiplyBy(2);
    // or
    result = new Calculator(5).Add(10).MultiplyBy(2).Equals();
    
    Console.WriteLine(result); // 30

    Код може виглядати трохи інакше залежно від реалізації, але основна ідея полягає в тому, щоб використовувати методи для виконання операцій у ланцюжковий спосіб (тобто виклики мають іти один за одним).

    • Реалізуйте 4 основні методи (сигнатури опущено для гнучкості ваших рішень):
      • Add()
      • Subtract()
      • MultiplyBy()
      • DivideBy()
    • Не соромтеся додавати складніші методи, щоби зробити калькулятор більш функціональним

    Існує принаймні 2 способи вирішення цього завдання. Але радимо спробувати й інші. Використовуйте підказки, якщо застрягли:

    • Перша підказка Цей спосіб передбачає «розширення» 😉 функціональності відомих типів
    • Друга підказка А цей — занурення в тему патерн дизайну (особливо тих, що стосуються будівельних матеріалів 🏗️

    Не соромтеся придумувати власні підходи.

    Не забувайте, що ми створюємо програмне забезпечення для звичайних користувачів, які можуть робити помилки: замість чисел вводити звичайний текст, ділити на 0 і так далі. Дозвольте користувачам використовувати не тільки цілі числа, а й числа з плаваючою точкою. Намагайтеся обробляти якомога більше edge cases, щоби зромити додаток більш стабільним.

    Розгорнути правильну відповідь з поясненням
  • [.NET] Який із блоків catch буде задіяний у результаті виконання коду?
    static void Recursive()
    {
        Recursive();
    }
    
    try
    {
        Recursive();
    }
    catch (InsufficientMemoryException)
    {
        // block 1
        throw;
    }
    catch (OutOfMemoryException)
    {
        // block 2
        throw;
    }
    catch (StackOverflowException)
    {
        // block 3
        throw;
    }
    catch (Exception)
    {
        // block 4
        throw;
    }

    a) block 1
    b) block 2
    c) block 3
    d) block 4
    e) жодного блоку не буде задіяно

    Розгорнути правильну відповідь з поясненням

    Насамперед пригадаємо як працює конструкція try…catch при наявності декількох блоків catch: при виникненні виключення виконується послідовне, починаючи згори, порівняння типу аргументу блоку catch із типом об’єкту помилки. І якщо тип аргументу співпадає із типом помилки, або ж якщо є серед базових типів класу помилки – виконується блок catch. Наступні ж блоки ігноруються. До речі, у catch блоках тип аргументу не може повторюватися. Конструкції throw в середні catch призводять до того, що помилка ”передається на рівень вище”, і навіть якщо після поточного блоку catch є інший блок, який може перехопити цю помилку за її типом, він не буде виконаний.

    Тепер проаналізуємо для яких виключень призначений кожен блок:

    InsufficientMemoryException – виключення свідчить що при перевірці доступної пам’яті виявилось шо її недостатньо. Це виключення виникає лише при явному використанні об’єкту MemoryFailPoint. Є похідний типом від OutOfMemoryException.

    OutOfMemoryException – повідомляє по для продовження виконання програми недостатньо пам’яті. Виключення виникає, або при перевищенні максимального значення довжини для StringBuilder, або коли CLR не може виділити достатню неперервну кількість пам’яті для виконання операції (створення об’єкту, виклик методу, тощо). Ця помилка пов’язана із пам’яттю купи.

    StackOverflowException – виникає при переповненні стека виконання, оскільки він містить занадто багато вкладених викликів методів. Якщо ця помилка не викинута явно (через throw), то вона не може бути перехоплена у try…catch.

    Exception – базовий тип для всіх винятків. Використовується для оголошення catch для будь-якої помилки.

    А тепер розглянемо що відбувається у блоці try: виконується виклик методу Recursive(). Цей метод не використовує MemoryFailPoint та не створює і не модифікує об’єкти, а отже блоки 1 та 2 не будуть задіяні. Натомість метод продукує неконтрольовану рекурсію, а отже буде переповнено стек і буде виключення StackOverflowException, яке згенеровано системою. Отже блоки 3 та 4 також не будуть задіяні.

    Правильна відповідь: e) жодного блоку не буде задіяно.

  • [.NET] В рамках цього weekly challenge вам треба створити веб API з in-memory базою даних, в якій буде 20 елементів (тобто список даних створений безпосередньо в самій програмі).Weekly Challenge

    Кожен елемент цього списку повинен мати поля з ім'ям (Name) та датою створення (CreationDate). Всі інші поля на ваш розсуд. Веб API має надавати користувачу можливість отримати наступні набори даних:

    1. Отримати перші 5 елементів відсортованих за ім'ям в алфавітному порядку
    2. Отримати перші 5 елементів відсортованих за датою створення у спадаючому порядку
    3. Отримати всі елементи з ім'ям довшим за 5 літер

    P.S. Ви вільні використовувати будь-які бібліотеки що можуть допомогти вам створити правдоподібні дані для вашого списку або ви можете створити ці дані за тематикою у якій ви зацікавлені, вибір за вами. P.P.S Для тестування вашої програми можна використати, наприклад, Postman. Відчуття, коли власноруч створений ендпоінт отримує перший запит, ви запам'ятаєте надовго.

    Розгорнути правильну відповідь з поясненням