February 16

Разбираем базовый уровень C#

C# — это язык, который поражает своей элегантностью и лаконичностью, но при этом способен решать задачи любого уровня сложности. В мире разработки платформы .NET он стоит особняком: строгая типизация, современный синтаксис и богатая экосистема делают C# истинным профессионалом среди языков. Как и в любом серьёзном деле, прежде чем погрузиться в проект, разработчики часто задаются вопросами: какой механизм здесь применить, какие возможности у типизации, как работает полиморфизм, какие тонкости в работе с файлами?

Чтобы разобраться со всем этим, мы можем использовать практику тестирования: от самых базовых знаний по типам данных до нюансов, связанных с LINQ, наследованием, инкапсуляцией и файловыми операциями. И именно такой тест, где последовательно проверяются ключевые аспекты языка, мы рассмотрим в этом разборе.

Вопрос 1. Сколько байтов занимает тип char в C#?

Варианты ответа:

  1. 1 байт
  2. 16 байт
  3. 8 байт
  4. 2 байта
  5. 4 байта

Обоснование:
В C# тип char представляет собой символ в кодировке UTF-16, который занимает 2 байта (16 бит) в памяти. В отличие от C++, где char может занимать 1 байт, в C# каждый символ представлен в формате System.Char, использующем 16-битное представление.

Правильный ответ:
2 байта

Вопрос 2. Что выведет следующая программа?

Варианты ответа:

  1. Ошибка компиляции (Compile-time error)
  2. 0
  3. Случайное значение
  4. Ошибка выполнения (Runtime error)
  5. Null

Обоснование:
В C# локальные переменные (переменные, объявленные внутри метода) не инициализируются автоматически. Переменная i объявлена, но не была присвоена никакого значения перед использованием в Console.WriteLine(i);. В отличие от переменных-членов класса, которые инициализируются значением по умолчанию (например, 0 для int), локальные переменные должны быть инициализированы перед использованием, иначе произойдет ошибка компиляции.

Правильный ответ:
Ошибка компиляции (Compile-time error)

Вопрос 3. Укажите результат работы фрагмента программы:

Варианты ответа:

  1. bcaab
  2. bcbcb
  3. abccb
  4. bbbcc
  5. bbbcb

Обоснование:
Разберём пошагово выполнение кода:

  1. a = 'b'; → a = 'b'
  2. b = 'c'; → b = 'c'
  3. c = a; → c = 'b' (так как a = 'b')
  4. b = c; → b = 'b' (теперь b перезаписывается значением c, а c было 'b')

Теперь вызов Console.WriteLine("{0}{1}{2}{3}{4}", a, b, c, 'c', b); подставляет текущие значения переменных:

  • {0} → a = 'b'
  • {1} → b = 'b'
  • {2} → c = 'b'
  • {3} → 'c' (явно переданное значение)
  • {4} → b = 'b'

В результате программа выведет: "bbbcb"

Правильный ответ:
bbbcb

Вопрос 4. Какое ключевое слово следует применить для запрета наследования от определенного класса?

Варианты ответа:

  1. Static
  2. Abstract
  3. Internal
  4. Const
  5. Sealed

Обоснование:
В C# для запрета наследования от класса используется ключевое слово sealed. Оно предотвращает создание производных классов, тем самым ограничивая возможность расширения класса.

Разберём остальные варианты:

  • Static – делает класс статическим, но не запрещает наследование (статические классы сами по себе не могут быть унаследованы).
  • Abstract – наоборот, требует, чтобы класс был унаследован, но не запрещает наследование.
  • Internal – ограничивает доступ к классу внутри одной сборки, но не запрещает наследование.
  • Const – используется для объявления констант, не применяется к классам.

Правильный ответ:
Sealed

Вопрос 5. Что такое полиморфизм в C#?

Варианты ответа:

  1. Процесс, который ограничивает доступ к некоторым компонентам объекта
  2. Процесс объединения данных и функций в единый компонент
  3. Возможность класса скрывать его внутренние данные и защищать их от несанкционированных изменений
  4. Механизм, который позволяет объектам вести себя как объекты другого типа
  5. Концепция, позволяющая объектам разных типов обрабатываться одинаково через общий интерфейс

Обоснование:
Полиморфизм – это один из основных принципов объектно-ориентированного программирования, который позволяет объектам разных типов обрабатываться одинаково через общий интерфейс или базовый класс. Это означает, что метод может иметь разное поведение в зависимости от типа объекта, который его вызывает.

В C# полиморфизм реализуется через:

  • Перегрузку методов (Method Overloading) – несколько методов с одним именем, но разными параметрами.
  • Переопределение методов (Method Overriding) – производный класс переопределяет методы базового класса.
  • Интерфейсы (Interfaces) – реализация нескольких типов через общий интерфейс.

Рассмотрим другие варианты:

  • Ограничение доступа к компонентам объекта – это инкапсуляция.
  • Объединение данных и функций – это абстракция.
  • Сокрытие внутренних данных и защита от изменений – это также инкапсуляция.
  • Объекты ведут себя как объекты другого типа – это скорее наследование и приведение типов, но не полиморфизм.

Правильный ответ:
Концепция, позволяющая объектам разных типов обрабатываться одинаково через общий интерфейс

Вопрос 6. Имеется два класса и следующий код. Что происходит при вызове a.Write()?

class A
{
public string InformationalString { get; set; }
public A(string info) => InformationalString = info;
public virtual void Write() => Console.WriteLine(InformationalString);
}
class B : A
{
public string AdditionalInformationalString { get; set; }
public B(string info, string additionalInfo): base(info) => AdditionalInformationalString = additionalInfo;
public override void Write() => Console.WriteLine(quot;{InformationalString} has some {AdditionalInformationalString}");
}
A a = new B("object", "properties");
a.Write();

Разбор кода:

1.      Класс A содержит:

    • Свойство InformationalString
    • Конструктор, принимающий строку
    • Виртуальный метод Write(), который выводит InformationalString

2.      Класс B (наследуется от A) содержит:

    • Дополнительное свойство AdditionalInformationalString
    • Конструктор, принимающий два аргумента
    • Переопределенный метод Write(), который выводит строку:
Console.WriteLine(quot;{InformationalString} has some {AdditionalInformationalString}");

3.      Создание объекта:

A a = new B("object", "properties");

Здесь a объявлен как объект типа A, но он фактически является экземпляром B (используется полиморфизм).

4.      Вызов a.Write():

  • Поскольку метод Write() является виртуальным (virtual) в A и переопределён (override) в B, то даже если переменная a имеет тип A, будет вызван метод из B.
  • Следовательно, выведется

object has some properties

Выбор правильного ответа:
Выполняется реализация метода Write из класса B, несмотря на то что переменная a — переменная типа A

Вопрос 7. Какой интерфейс должен реализовать класс, чтобы обеспечить выполнение запросов с использованием LINQ?

Варианты ответа:

  1. ILinq или IQueryable
  2. IEnumerable или IQueryable
  3. IEnumerable или ILinq
  4. IEnumerator или IQueryable
  5. IEnumerator или ILinq

Обоснование:
LINQ (Language Integrated Query) выполняет запросы к коллекциям данных, и для этого используются два ключевых интерфейса:

  1. IEnumerable<T> – используется для работы с коллекциями данных в памяти. Запросы LINQ выполняются лениво (lazy evaluation), перебирая элементы с помощью foreach.
  2. IQueryable<T> – используется для работы с удалёнными источниками данных (например, базы данных через Entity Framework). Этот интерфейс позволяет строить запросы, которые затем транслируются в SQL.

Другие варианты:

  • ILinq – такого интерфейса в C# не существует.
  • IEnumerator – используется для перебора элементов в коллекции, но не поддерживает выполнение LINQ-запросов.
  • ILinq – не является стандартным интерфейсом в .NET.

Правильный ответ:
IEnumerable или IQueryable

Вопрос 8. У вас есть следующий список:

Вам необходимо получить все числа больше 65. Какой вариант для этого подходит?

Варианты ответа:

  1. var result = items.Take(65);
  2. var result = items.Find(i => i > 65);
  3. var result = items.Select(i => i > 65);
  4. var result = items.Where(i => i > 65).Select(i);
  5. var result = items.Any(i => i > 65);

Разбор вариантов:

  1. Take(65) – Этот метод берет первые 65 элементов из списка, но не фильтрует их. Ошибка в логике. ❌
  2. Find(i => i > 65) – Метод Find() возвращает только первый элемент, который удовлетворяет условию, а нам нужен список. ❌
  3. Select(i => i > 65) – Метод Select() проецирует (преобразует) каждый элемент, но не фильтрует. В данном случае он вернёт List<bool>. ❌
  4. Where(i => i > 65).Select(i) – Здесь ошибка синтаксиса: .Select(i) требует указания, что именно нужно выбрать. Однако если исправить на .Select(i => i), то это будет правильный ответ. ✅
  5. Any(i => i > 65) – Метод Any() возвращает true или false, если есть хотя бы один элемент, подходящий под условие, но не список чисел. ❌

Правильный ответ:

var result = items.Where(i => i > 65).Select(i => i); (с исправленным .Select(i => i))

Вопрос 9. Какой класс необходимо использовать на месте пропуска в коде ниже, чтобы получить список файлов, упорядоченный по времени создания?

Варианты ответа:

  1. Path
  2. Directory
  3. File
  4. FileInfo
  5. DirectoryInfo

Обоснование:

  1. Что делает код?
    • Создаёт объект некоторого класса, работающего с файловой системой.
    • Получает все файлы в текущем каталоге с помощью .GetFiles("*").
    • Сортирует файлы по времени их создания (CreationTime).
    • Преобразует результат в список .ToList().
  2. Какой класс подходит?
    • Path – Это статический класс, предназначенный для работы с путями, он не предоставляет метода .GetFiles(). ❌
    • Directory – Это статический класс, он не создаёт объекты, а работает с каталогами через статические методы. ❌
    • File – Используется для работы с файлами, но не предоставляет .GetFiles() и не создаёт объекты. ❌
    • FileInfo – Предоставляет информацию о конкретном файле, но не используется для работы со списками файлов. ❌
    • DirectoryInfo – Этот класс представляет каталог, и у него есть метод .GetFiles(), возвращающий массив FileInfo. ✅

Вывод:
DirectoryInfo – правильный ответ, так как этот класс позволяет получить список файлов в каталоге и упорядочить их по CreationTime.

Вопрос 10. Что делает следующий код?

Варианты ответа:

  1. Открывает файл на запись и чтение. Если файл существует, дописывает в него
  2. Создаёт новый файл и записывает в него текст. Если файл существует, перезаписывает его
  3. Создаёт новый файл и записывает в него текст. Если файл существует, вызывает исключение
  4. Создаёт новый файл и записывает в него текст. Если файл существует, то дописывает в него
  5. Открывает файл на запись и чтение. Если файл существует, перезаписывает его

Разбор кода:

Метод File.WriteAllText(path, content) в C# выполняет следующие действия:

  • Если файл не существует, создаёт его.
  • Если файл уже существует, перезаписывает его (удаляет старое содержимое).
  • Записывает переданный текст в файл.
  • Не дописывает текст в уже существующий файл (в отличие от File.AppendAllText).
  • Не вызывает исключение, если файл существует.

Выбор правильного ответа:

"Создаёт новый файл и записывает в него текст. Если файл существует, перезаписывает его"

Заключение

В языке C# всё сконструировано так, чтобы помочь разработчику оставаться эффективным и создавать надёжные решения. От разветвлённой системы типов до продвинутых возможностей LINQ — каждая деталь работает синхронно. Постепенное углубление в механику языка через подобные тесты не только проверяет наши знания, но и развивает навыки реальной разработки: будь то умение грамотно переопределять методы или уверенно управлять файлами.

Всё, что мы обсудили в тесте, отражает базовые принципы C#, но главное — это лишь часть обширного мира .NET. Если вы хотите почувствовать всю мощь экосистемы, идите дальше, пробуйте свои силы в проектах, исследуйте новые библиотеки и подходы. Но базис, который мы сегодня закрепили, даст вам достаточно прочный фундамент, чтобы двигаться вперёд и создавать по-настоящему первоклассный код