Вопросы и ответы с собеседований по C# и .NET
Вопросы и ответы с собеседований для C# и .NET разработчиков. Типы данных, модификаторы доступа, особенности реализации наследования, сборка мусора, устройство компиляции .NET, LINQ и отложенное выполнение, делегаты и многое другое.
Какие типы данных есть в C#?
bool
(System.Boolean): true/false.
byte
(System.Byte): целое число от 0 до 255, занимает 1 байт.
sbyte
(System.SByte): целое число от -128 до 127, занимает 1 байт.
short
(System.Int16): целое число от -32768 до 32767, занимает 2 байта.
ushort
(System.UInt16): целое число от 0 до 65535, занимает 2 байта.
int
(System.Int32): целое число от -2147483648 до 2147483647, занимает 4 байта.
uint
(System.UInt32): целое число от 0 до 4294967295, занимает 4 байта.
long
(System.Int64): целое число от –9 223 372 036 854 775 808 до 9 223 372 036 854 775 807, занимает 8 байт.
ulong
(System.UInt64): целое число от 0 до 18 446 744 073 709 551 615, занимает 8 байт.
float
(System.Single): число с плавающей точкой от -3.4*1038 до 3.4*1038, занимает 4 байта.
double
(System.Double): число с плавающей точкой от ±5.0*10-324 до ±1.7*10308, занимает 8 байта.
decimal
(System.Decimal): десятичное дробное число. Если употребляется без десятичной запятой, имеет значение от ±1.0*10-28 до ±7.9228*1028, может хранить 28 знаков после запятой, занимает 16 байт.
char
(System.Char): одиночный символ в кодировке Unicode, занимает 2 байта.
string
(System.String): набор символов Unicode.
object
(System.Object): значение любого типа данных, занимает 4 байта на 32-разрядной платформе и 8 байт на 64-разрядной платформе.
Что такое System.Object?
Все классы в .NET, даже те, которые мы сами создаем, а также базовые типы, такие как System.Int32
, являются неявно производными от класса System.Object
. Даже если мы не указываем класс System.Object
в качестве базового, по умолчанию он всё равно стоит на вершине иерархии наследования. Поэтому все типы и классы могут реализовать те методы, которые определены в System.Object
, а именно:
1. ToString
служит для получения строкового представления данного объекта.
2. GetHashCode
позволяет возвратить некоторое числовое значение, которое будет соответствовать данному объекту или его хэш-код.
3. GetType
позволяет получить тип данного объекта.
4. Equals
позволяет сравнить два объекта на равенство.
Какие операции со строками вы знаете?
1. Конкатенация - string.Concat(s1, s2)
2. Сравнение - string.Compare(s1, s2)
3. Поиск в строке - s1.IndexOf(char)
4. Разбитие/разделение - s1.Split(new char[] { char })
5. Обрезка - s1.Trim()
или s1..Trim(new char[] { char1, char2 })
6. Вставка - s1.Insert(index, substring)
7. Удаление части строки - s1.Remove(index, count)
8. Замена - s1.Replace(a, b)
9. Смена регистра - s1.ToLower()
и s1.ToUpper()
Какие модификаторы доступа есть в C#?
Все типы и члены типов имеют уровень доступности. Он определяет возможность их использования из другого кода в вашей или в других сборках. Сборка — это .dll или .exe, созданные путем компиляции одного или нескольких CS-файлов в одной компиляции. Следующие модификаторы доступа позволяют указать доступность типа или члена при объявлении:
public
: доступ к типу или члену возможен из любого другого кода в той же сборке или другой сборке, ссылающейся на него. Уровень доступности общедоступных элементов типа определяется уровнем доступности самого типа.
private
: доступ к типу или члену возможен только из кода в том же объекте class
или struct
.
protected
: доступ к типу или члену возможен только из кода в том же объекте class
, либо в class
, производном от этого class
.
internal
: доступ к типу или члену возможен из любого кода в той же сборке, но не из другой сборки. Другими словами, internal
доступ к типам или членам можно получить из кода, который является частью той же компиляции.
protected internal
: доступ к типу или члену возможен из любого кода в той сборке, где он был объявлен, или из производного class
в другой сборке.
private protected
: доступ к типу или элементу возможен из типов, производных от объекта class
и объявляемых в сборке, содержащей этот объект.
В чём разница между const и readonly?
Значение const
фиксируется на этапе компиляции и не может быть изменено во время исполнения кода.
Значение readonly
фиксируется на этапе исполнения в конструкторе класса. Из конструктора может меняться и многократно.
Поддерживает ли C# множественное наследование?
С# поддерживает множественное наследование в виде наследования от класса и нескольких интерфейсов, или просто от нескольких интерфейсов. Но не поддерживает наследование от нескольких классов.
Как запретить наследование от класса?
Для этого служит ключевое слово sealed
.
Можно ли разрешить наследование класса, но запретить перекрытие метода?
Да. Указываем класс как public
, а метод как sealed
.
Разница между управляемым и неуправляемым кодом?
Управляемый код – это код, созданный компилятором .NET. Он не зависит от архитектуры целевой машины, поскольку выполняется CLR (Common Language Runtime), а не самой операционной системой. CLR и управляемый код предлагают разработчикам несколько преимуществ, таких как сборка мусора, проверка типов и обработка исключений. С другой стороны, неуправляемый код напрямую компилируется в родной машинный код и зависит от архитектуры целевой машины. Он выполняется непосредственно операционной системой. В неуправляемом коде разработчик должен убедиться, что он вручную решает вопросы использования и выделения памяти (особенно из-за утечек памяти), безопасности типов и исключений. В .NET компилятор Visual Basic и C# создает управляемый код. Чтобы получить неуправляемый код, приложение должно быть написано на C или C++.
Что такое упаковка и распаковка?
Упаковка и распаковка значимых типов в C# — механизм преобразования размерных типов данных языка C# из значимых в ссылочные и обратно через задействование свойств фундаментального базового класса Object
.
Как правило, значимые типы предпочтительнее для использования, чем ссылочные: для них не нужно заботиться о динамическом выделении памяти из «кучи», на них не расходуются ресурсы при сборке мусора, по отношению к ним нет необходимости использовать адресацию через указатели.
Однако, иногда необходимо использовать значимые типы совместно со ссылочными. Помимо этого, для использования ряда библиотечных классов нередко возникает нужда в ссылках на экземпляры значимых типов, что заставляет применять механизм их упаковки (boxing) с перспективой распаковки (unboxing) в будущем.
Что такое сборка мусора?
Сборка мусора – это низкоприоритетный процесс, который служит в качестве автоматического менеджера памяти, управляющего распределением и освобождением памяти для приложений.
Каждый раз, когда создается новый объект, среда выполнения общего языка выделяет память для этого объекта из управляемой кучи. Пока в управляемой куче есть свободное пространство памяти, среда выполнения продолжает выделять место для новых объектов. Однако память не бесконечна, и как только приложение заполняет пространство, в дело вступает сборщик мусора (Garbage Collector), который освобождает часть памяти.
Когда сборщик мусора выполняет очистку, он проверяет объекты в управляемой куче, которые больше не используются приложением, и выполняет необходимые операции. Сборщик мусора останавливает все запущенные потоки, находит все объекты в куче, к которым не обращается основная программа, и удаляет их. Затем он собирает все объекты, оставшиеся в куче, чтобы освободить место, и корректирует все указатели на эти объекты в стеке и в куче.
Чтобы запустить сборку мусора в коде вручную, выполняем System.GC.Collect()
В чём разница между stack и heap?
В stack
хранятся типы значений (типы, унаследованные от System.ValueType
), а в heap
– ссылочные типы (типы, унаследованные от System.Object
).
Можно сказать, что stack
отвечает за отслеживание того, что на самом деле выполняется и где находится каждый выполняющийся поток (каждый поток имеет свой собственный стек). Heap
, с другой стороны, отвечает за хранение данных, или, более точно, объектов.
Как устроена компиляция в .NET?
IL (Intermediate Language) – это независимый от процессора частично скомпилированный код. Код IL будет скомпилирован в родной машинный код с использованием текущих свойств среды компилятором Just-In-Time (JIT). JIT-компилятор переводит IL-код в код сборки и использует архитектуру процессора целевой машины для выполнения приложения .NET. В .NET язык IL называется Common Intermediate Language (CIL), а на первых этапах .NET он назывался Microsoft Intermediate Language (MSIL). CLI (Common Language Infrastructure) – это открытая спецификация, разработанная компанией Microsoft. Это библиотека скомпилированного кода, используемая для развертывания, создания версий и обеспечения безопасности. В .NET существует два типа CLI: сборки процессов (EXE) и сборки библиотек (DLL). Сборки CLI содержат код на языке CIL, и, как уже упоминалось, при компиляции языков программирования CLI исходный код транслируется в код CIL, а не в объектный код, специфичный для платформы или процессора. Подведем итоги: 1. При компиляции исходный код сначала транслируется в IL (в .NET это CIL, а ранее назывался MSIL). 2. Затем CIL собирается в байткод и создается сборка CLI. 3. Перед выполнением кода CLI-код проходит через JIT-компилятор среды выполнения для генерации родного машинного кода. 4. Процессор компьютера выполняет родной машинный код.
В чем разница между интерфейсом и абстрактным классом в .NET?
// Абстрактный класс
public abstract class BaseClass
{
// Абстрактный метод
public abstract void SomeMethod();
public void OtherMethod()
{
Console.WriteLine("Other method in BaseClass");
}
}
Абстрактный класс – это класс, содержащий один или несколько абстрактных методов. Особенностью такого класса является то, что нельзя создать объект данного класса. Это связано с тем, что абстрактный метод, как правило, не имеет реализации. Чтобы метод или класс был абстрактным, его следует пометить ключевым словом abstract
. Абстрактные классы обычно используются для определения базового класса в иерархии классов.
Интерфейс – это абстрактный ссылочный тип, который может содержать некоторое количество методов, свойств, событий и индексаторов. Начиная с версии C# 8.0 ещё и статические поля и константы. Так же, как и с абстрактным классом, нельзя создать объект данного типа, но мы можем объявить, например, сигнатуры нужных нам методов.
Что такое виртуальный метод?
public virtual double Area()
{
return x * y;
}
При наследовании нередко возникает необходимость изменить в классе-наследнике функционал метода, который был унаследован от базового класса. В этом случае класс-наследник может переопределять методы и свойства базового класса.
Те методы и свойства, которые мы хотим сделать доступными для переопределения, в базовом классе помечаются модификатором virtual
. Такие методы и свойства называют виртуальными.
А чтобы переопределить метод в классе-наследнике, этот метод определяется с модификатором override
. Переопределенный метод в классе-наследнике должен иметь тот же набор параметров, что и виртуальный метод в базовом классе.
Что такое LINQ?
LINQ — Language Integrated Query (Внутриязыковой запрос) — технология, представляющая собой набор функций, позволяющих писать структурированные типобезопасные запросы к локальным объектам-коллекциям и удаленным источникам данных.
С помощью LINQ можно писать запросы к любой коллекции, реализующей интерфейс IEnumerable<>
, например, к массивам, спискам (list), XML DOM, удаленным источникам данных, таким как таблицы SQL сервера. LINQ объединяет преимущества динамических запросов и проверки типов при компиляции.
Что такое отложенное и немедленное выполнение в LINQ?
DataContext productContext = new DataContext();
var productQuery = from product in productContext.Products
where product.Type == "SOAPS"
select product; // запрос не выполняется
foreach (var product in productQuery)
{
Console.WriteLine(product.Name);
}
В LINQ отложенное выполнение означает, что запрос не выполняется в указанное время. В частности, это достигается путем присвоения запроса переменной. При этом определение запроса сохраняется в переменной, но запрос не выполняется до тех пор, пока переменная запроса не будет итерирована.
Немедленное выполнение запроса полезно, например, если база данных часто обновляется, и в логике программы важно, чтобы результаты, к которым обращаются, были возвращены в той точке вашего кода, где был задан запрос. Немедленное выполнение часто вызывается с помощью таких методов, как Average
, Sum
, Count
, List
, oList
или ToArray
.
Что такое делегаты в .NET?
public delegate void FooDelegate();
class FooClass
{
// кастомное событие
public event FooDelegate FooEvent;
}
FooClass FooObj = new FooClass()
FooObj.FooEvent += new FooDelegate();
Делегат — это объектно-ориентированный способ работы с методом как с переменной. Его более привычный аналог — указатель на функцию, функтор, или даже просто вектор прерывания.
Делегат представляет собой тип, соответствующий определённой сигнатуре функции. Объявив переменную делегатного типа, вы можете записать в неё статический или нестатический метод, передать его как аргумент куда-либо, и вызвать.
Классический пример использования делегатов — сортировка списка объектов по значению какого-либо поля. Вы передаёте в сортирующий метод делегат, который по объекту вычисляет ключ сортировки, то есть, вытаскивает значение поля.
Что такое ref и out?
ref
и out
позволяют передавать в метод ссылки на объекты и для value type
, и для reference type
.
И ref
, и out
позволяют внутри метода использовать new
и для class
, и для struct
.
out
отличается от ref
тем, что в его случае метод обязательно пересоздаст переменную.
Что такое generics?
Generics — это обобщенные типы. Они нужны для описания похожих, но отличающихся какими-то характеристиками типов. Мы описываем общую структуру, а конкретную уже определяет пользователь дженерика. Generics - мощная фича, доступная во многих статически типизированных языках программирования. С их помощью можно писать код, который работает со множеством разных типов, делая упор на их общие особенности, при этом позволяя различаться в определённых деталях.
Что такое GetHashCode?
GetHashCode
используется для ускорения сравнения двух объектов. То есть, если требуется узнать, одинаковы ли какие-то два объекта, то сначала сравниваются их хэш-коды. Если они различаются, то значит и объекты различны. Если же совпадают, то тогда начинается дорогостоящее "настоящее" сравнение через Equals
.
GetHashCode
не возвращает уникальный ключ/хеш объекта. Разные объекты, даже одного типа, могут возвращать одинаковое значение - и это будет корректно.
Соответственно, GetHashCode
нельзя использовать для сравнения объектов (только как вспомогательную функцию).
GetHashCode
нужен для быстрого поиска в хеш-таблицах. Такие объекты как HashSet<T>
и Dictionary<TKey, TValue>
используют в своей работе хеш-таблицы. Если объект используется в качестве ключа в хеш-таблице, то значение его GetHashCode
указывает на позицию в хеш-таблице. При этом на одной позиции может быть несколько разных элементов (у которых одинаковый GetHashCode
).
Что такое Finalize?
// Класс System.Object
public class Object
{
protected virtual void Finalize () {}
}
В главном базовом классе .NET — System.Object
— есть виртуальный метод Finalize()
. В реализации по умолчанию он ничего особенного не делает.
За счет его переопределения в специальных классах устанавливается специфическое место для выполнения любой необходимой данному типу логики по очистке. Из-за того, что метод Finalize()
по определению является защищенным (protected), вызывать его напрямую из класса экземпляра точечной нотацией не допускается. Вместо этого метод Finalize()
будет автоматически вызываться сборщиком мусора перед удалением соответствующего объекта из памяти.
Переопределять метод Finalize()
в типах структур нельзя. Это вполне логичное ограничение, поскольку структуры представляют собой типы значения, которые изначально никогда не размещаются в управляемой памяти и, следовательно, никогда не подвергаются процессу сборки мусора. Однако, в случае создания структуры, которая содержит ресурсы, нуждающиеся в очистке, вместо этого метода можно реализовать интерфейс IDisposable
.
Вызов метода Finalize()
будет происходить либо во время естественной активизации процесса сборки мусора, либо во время его принудительной активизации программным образом с помощью GC.Collect()
. Помимо этого финализатор типа будет автоматически вызываться и при выгрузке из памяти домена, который отвечает за обслуживание приложения.
В чем различие между Finalize и Dispose?
Dispose
обеспечивает явный контроль над ресурсами, используемыми объектом, а Finalize
- неявный метод, используемый сборщиком мусора.
В чём разница между throw и throw ex?
throw
повторно выбрасывает исключение (re-throw
), которое было обнаружено, и сохраняет трассировку стека (путь к источнику исключения).
throw ex
генерирует одно и то же исключение, но сбрасывает трассировку стека на метод, где делается throw ex
.
Что такое атрибут?
[
Атрибут (attribute) - это универсальное средство связи данных с типами, позволяют добавлять любую текстовую информацию о классах, свойствах, методах и т.д. Атрибуты сохраняются с метаданными и могут быть получены при выполнении программы.
Конструкторы атрибутов могут принимать именованные параметры — поля и свойства атрибутов. При задании именованного параметра в конструктор атрибута передается имя свойства или поля, которое должно принять этот параметр. Причем эти свойства и поля должны быть открытыми. Если именованные параметры передаются, то они обязательно должны идти следом за позиционными параметрами, которые явно указаны в конструкторе атрибута. Указание именованных параметров не является обязательным, поэтому при проектировании атрибутов необходимо помнить о том, что некоторые его поля или свойства могут быть не проинициализированы. ]
Что такое "as" и "is"?
Операторы приведения типов.
If (o is Employee) {
Employee e = (Employee) o;
}
С помощью оператора as
программа пытается преобразовать выражение к определенному типу, при этом не выбрасывает исключение. В случае неудачного преобразования выражение будет содержать значение null
.
Выражение o is Employee
проверяет, является ли переменная o
объектом типа Employee
.