Вопрос – Ответ
January 22, 2019

Вопросы на собеседованиях по C# и .Net. На позицию junior/middle. Часть 3.


!!НАШ БЛОГ ПЕРЕЕХАЛ!!

Мы создали свой сайт! Все материалы, опубликованные в этом блоге, переехали туда.

Наш новый сайт maddevelop.ru

А данную статью вы можете найти по ссылке ниже:

https://maddevelop.ru/Textbook/ViewSelectedArticle?textbookName=C%23&sectionName=%D0%A1%D0%BE%D0%B1%D0%B5%D1%81%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D0%BF%D0%BE%20C%23%20%D0%B8%20.NET&subsectionName=%D0%A1%D0%BE%D0%B1%D0%B5%D1%81%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D0%BD%D0%B0%20%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8E%20junior%2Fmiddle&articleName=%233%20%D0%91%D0%BB%D0%BE%D0%BA%20%D0%B2%D0%BE%D0%BF%D1%80%D0%BE%D1%81%D0%BE%D0%B2%20(%D0%B2%D0%BE%D0%BF%D1%80%D0%BE%D1%81%D1%8B%2011-20)

P.S. движок teletype.in очень странно интерпретирует ссылку, и пришлось вставлять ее полным текстом


Вопрос 11

Какова алгоритмическая сложность для операций чтения и записи для коллекции Dictionary?

  1. Чтение - О(1), запись - О(1)
  2. Чтение - О(n), запись - О(1)
  3. Чтение - О(n), запись - О(n)
  4. Чтение - О(1), запись - О(n)

Ответ: Согласно MSDN ответ может быть неоднозначный.

Точно можно сказать, что чтение очень быстрое, потому что используются хэш-таблицы и сложность в этом случае стремится к O(1).

Retrieving a value by using its key is very fast, close to O(1), because the Dictionary<TKey,TValue> class is implemented as a hash table.

Источник.

Запись проходит тоже очень быстро ( O(1) ), в том случае если .Count меньше емкости, если же больше, то скорость стремится к O(n).

If Count is less than the capacity, this method approaches an O(1) operation. If the capacity must be increased to accommodate the new element, this method becomes an O(n) operation, where n is Count.

Источник

Вопрос 12

В чем различие между ключевыми словами "ref" и "out"?

  1. Параметр с ключевым слово ref может быть не инициализирован, а параметр с ключевым словом out обязательно должен быть инииализирован до вызова метода, который использует эти параметры
  2. Параметр с ключевым слово out может быть не инициализирован, а параметр с ключевым словом ref обязательно должен быть инииализирован до вызова метода, который использует эти параметры
  3. Нет различий
  4. Ключевым слово ref может использоваться только со значимыми типами (value types), а out может использоваться как со значимыми, так и с ссылочными типами.

Ответ: Правильный ответ под номером 2.

Разница в том, что out — это выходной параметр, а ref — входно-выходной.

ref-параметр нужно передать инициализированным, но так же можно пользоваться его исходным значением. А out-параметр не обязательно инициализировать перед вызовом функции, но необходимо инициализировать его в теле функции.

(Таким образом, ref-параметр немного напоминает инициализированную локальную переменную, а out-параметр — неинициализированную.)

Пример:

private void func1(out string value)
{
    Console.WriteLine(value); // нельзя, value не инициализировано
    if (false)
        return;               // нельзя, забыли установить значение value
    value = "Hello World!";
}

string s1;
func1(out s1);
private void func2(ref string value)
{
    Console.WriteLine(value); // можно
    if (false)
        return;               // не проблема, у value остаётся старое значение
    value = "Hello World!";
}

string s2;
func2(ref s2); // нельзя, функция имеет право использовать значение,
               // значит, оно должно быть инициализировано сначала

Таким образом, out-параметр — это как бы дополнительное возвращаемое значение функции. А ref-параметр — просто параметр, изменения которого видны снаружи функции.

Вопрос 13

Какой будет результат вызова следующего кода:

static void Main(string[] args)
{
    object sync = new object();
    var thread = new Thread(()=>
    {
        try
        {
            Work();
        }
        finally
        {
            lock (sync)
            {
                Monitor.PulseAll(sync);
            }
        }
    });
    thread.Start();
    lock (sync)
    {
        Monitor.Wait(sync);
    }
    Console.WriteLine("test");
}
private static void Work()
{
    Thread.Sleep(1000);
}

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

  1. Будет выброшено SynchronizationLockException
  2. Слово "test" не будет напечатано, так как произойдет взаимоблокировка (deadlock)
  3. В одних случаях может быть напечатано слово "test", а в других может произойти взаимоблокировка
  4. Всегда будет напечатано слово "test"

Ответ: Всегда будет напечатано слово "test". (Ответ 4)

Объяснить можно так. Стартует поток thread вызывающий метод Work, который усыпляет этот поток на секунду. В это же время основной поток получает блокировку lock(sync) и тут же высвобождает методом Monitor.Wait(sync). Основной поток становится в очередь ожидания вызова метода Pulse или PulseAll

Monitor.Wait(Object) - Освобождает блокировку объекта и блокирует текущий поток до тех пор, пока тот не получит блокировку снова.

К этому моменту проходит секунда, поток thread просыпается и пытается получить блокировку в блоке finally. Успешно ее получает, так как она освобождена. Посылает Pulse главному потоку, будит его и выводит его из очереди ожидания. lock заканчивается, блокировка снимается, аналогично lock основного потока. Выполняется WriteLine.

Вопрос 14

Какой результат выполнения будет у следующего кода:

class MyCustomException : DivideByZeroException
{
 
}
static void Main(string[] args)
{
    try
    {
        Calc();
    }
    catch (MyCustomException e)
    {
        Console.WriteLine("Catch MyCustomException");
        throw;
    }
    catch (DivideByZeroException e)
    {
        Console.WriteLine("Catch Exception");
    }
    Console.ReadLine();
}
 
private static void Calc()
{
    int result = 0;
    var x = 5;
    int y = 0;
    try
    {
        result = x / y;
    }
    catch (MyCustomException e)
    {
        Console.WriteLine("Catch DivideByZeroException");
        throw;
    }
    catch (Exception e)
    {
        Console.WriteLine("Catch Exception");
    }
    finally
    {
        throw new MyCustomException();
    }
}

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

  1. Catch DivideByZeroException, Catch MyCustomException
  2. Catch Exception, Catch MyCustomException
  3. Catch DivideByZeroException, Catch DivideByZeroException
  4. Catch Exception, Catch DivideByZeroException

Ответ: программа выведет Catch Exception, Catch MyCustomException (ответ 2).

Деление на ноль в блоке try генерит DivideByZeroException, поэтому сначала перехватывается второй catch с выводом Catch Exception. Далее в блоке finaly генерится исключение MyCustomExceprion и поднимается по стеку к месту вызова метода Calc, тем самым срабатывает catch с MyCustomException и выводится на консоль Catch MyCustomException. Но так же в этом блоке есть дополнительный throw, который заново вызывает exception, который уже не будет обработан.

Вопрос 15

В чем отличие необязательных параметров от именованных?

Ответ: Необязательные параметры позволяют опускать аргументы функции, в то время как именованные параметры разрешают передавать аргументы по названию параметра.

Можно, например присвоить второму и третьему параметру функции значения по-умолчанию:

public void optionalParamFunc(int p1, int p2 = 2, int p3 = 3); 

После этого при вызове optionalParamFunc можно опустить второй и третий параметр:

optionalParamFunc(1); //это эквивалентно optionalParamFunc(1,2,3);  

Также, можно передавать значения параметров по названию:

optionalParamFunc(1, p3:10); //это эквивалентно optionalParamFunc(1,2,10); 

Вопрос 16

Чем отличаются друг от друга классы String и StringBuilder? Зачем нужно такое разделение?

Ответ: объект класса String представляет собой неизменяемую строку. Когда выполняется какой-нибудь метод класса String, система создает новый объект в памяти с выделением ему достаточного места. Объект класса StringBuilder представляет собой динамическую строку. При создании строки StringBuilder выделяет памяти больше, чем необходимо этой строке, а при добавлении к ��ей каких-либо элементов строка не пересоздается заново. В том случае если выделенной памяти не будет хватать для добавления новых элементов, то емкость объекта будет увеличена. Подробнее про StringBuilder можно почитать на metanit

Вопрос 17

Какие отличие между значимыми и ссылочными типами?

Ответ: значимые типы (value type) хранятся в стеке. Стек - это структура данных, которая растет снизу вверх: каждый новый элемент помещаются поверх предыдущего. Время жизни переменных таких типов ограничено их контекстом. Физически стек - это некоторая область памяти в адресном пространстве. А ссылочные типы (reference type) хранятся в куче, это другая область памяти, которую можно представить как неупорядоченный набор различных объектов. Когда создаётся объект ссылочного типа в стеке помещается ссылка на адрес в куче. Когда этот объект перестает использоваться, то ссылка уничтожается, а память в куче очищается.

Вопрос 18

Как и зачем использовать конструкцию Using в C#?

Ответ: Ключевое слово Using упрощает работу с объектами которые реализуют интерфейс IDisposable.

Интерфейс IDisposable содержит один метод .Dispose(), который используется для освобождения ресурсов, которые захватил объект. При использовании Using не обязательно явно вызывать .Dispose() для объекта.

 using (SqlConnection conn = new SqlConnection()) { 
// какая-нибудь SQL операция 
} 

При этом компилятор генерирует следующий код:

SqlConnection conn = new SqlConnection(); 
try { 
 
} finally { 
    // здесь для conn вызывается .Dispose() 
} 

Вопрос 19

В чем отличие использования Finalize и Dispose?

Ответ: Dispose нужен для освобождения ресурсов "здесь и сейчас" (а точнее вызов Dispose сигнализирует, что нужно освободить ресурс, но не факт это случится в тот же момент). Необходимость и преимущество интерфейса IDisposable именно в том, что его реализация позволяет освобождать ресурсы не тогда, когда до них доберется сборщик мусора, а тогда, когда это нужно программисту. Ресурсы могут быть дорогими, и держать их в памяти неопределенно долгое время может быть слишком расточительным.

Что качается деструкторов в C#, то их нет вовсе, но есть финализаторы. Разница в том, что время вызова финализатора не определено. Его вызвать вручную нельзя это делается автоматически, в отличии от Dispose, который предназначен для такого.

Finalize выполняется перед уничтожением объекта. Можно сказать, что это "последний шанс" освободить ресурсы корректно. Определять этот метод имеет смысл только в случае, если класс имеет доступ к каким-либо неуправляемым ресурсам.

Вопрос 20

В чем основные отличия класса от структуры в языке C#?

Ответ: Основные отличия класса от структуры следующие: 

1. Структура является размерным типом, а класс – ссылочным.

2. Все структурные типы неявно наследуются от System.ValueType, они не бывают абстрактными и всегда неявно запечатаны (sealed)

3. При присваивании переменных структурного типа, создается копия данных

4. Объявления полей структуры не могут иметь инициализаторов

5. Различная интерпретация this для структуры и класса

6. Структура не может содержать конструктор без параметров

7. Структура не может содержать деструктор

8. Для ссылочных типов значение по умолчанию – null

9. При конвертировании между ссылочным и размерным типами происходит упаковка и распаковка.


Ещё больше интересной информации на нашем Telegram канале.

<< К части 2 << ......... >> К части 4 >>

Источники вопросов:

Metanit. Собеседование по C#. Часть 3

Metanit. Собеседование по C#. Часть 4