Классы и интерфейсы
Что такое класс?
Класс — это шаблон или описание для создания объектов, который объединяет данные (поля) и методы (функции) для работы с этими данными.
Чем отличается класс от абстрактного класса?
Основное различие между классом и абстрактным классом в .NET:
-
Классы:
-
Предоставляют конкретную реализацию.
-
Могут быть непосредственно инициализированы.
-
-
Абстрактные классы:
-
Это абстрактные шаблоны.
-
Требуют реализации конкретными производными классами.
-
Чем отличается абстрактный класс от интерфейса? Для чего нужны интерфейсы и какие задачи они выполняют?
Абстрактный класс и интерфейс в C# используются для определения контрактов, которые классы должны реализовать, но они имеют несколько ключевых отличий и применяются в разных ситуациях.
Отличия между абстрактным классом и интерфейсом
-
Наследование:
-
Абстрактный класс: Класс может наследоваться только от одного абстрактного класса.
-
Интерфейс: Класс может реализовывать множество интерфейсов.
-
-
Реализация методов:
-
Абстрактный класс: Может содержать как абстрактные методы (без реализации), так и методы с реализацией.
-
Интерфейс: Не содержит реализации методов (до C# 8.0). Начиная с C# 8.0, интерфейсы могут содержать реализации методов по умолчанию.
-
-
Полиморфизм:
-
Абстрактный класс: Поддерживает полиморфизм через виртуальные методы и абстрактные методы.
-
Интерфейс: Поддерживает полиморфизм через методы интерфейса, которые должны быть реализованы в классе.
-
-
Модификаторы доступа:
-
Абстрактный класс: Может содержать поля, свойства и методы с различными модификаторами доступа (private, protected, public и т.д.).
-
Интерфейс не хранит поля экземпляра. Начиная с C# 8 интерфейсы могут содержать реализации по умолчанию, статические поля и члены с модификаторами доступа; точные возможности зависят от версии C# и runtime.
-
Интерфейсы нужны для определения набора методов и свойств, которые класс должен реализовать, обеспечивая определенный контракт без указания способа его реализации. Это полезно для:
-
Поддержки полиморфизма: Позволяет использовать разные классы через общий интерфейс, обеспечивая гибкость и расширяемость кода.
-
Многоразовости кода: Класс может реализовывать несколько интерфейсов, что позволяет комбинировать функциональность из разных источников.
-
Снижения связности кода: Позволяет писать более гибкий и тестируемый код. Интерфейсы способствуют инверсии управления (IoC) и внедрению зависимостей (DI).
-
Упрощения тестирования: Интерфейсы облегчают создание мок-объектов для юнит-тестирования.
Пример использования интерфейса:
public interface IFlyable
{
void Fly();
}
public class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("Bird is flying.");
}
}
public class Airplane : IFlyable
{
public void Fly()
{
Console.WriteLine("Airplane is flying.");
}
}
public class Program
{
public static void Main(string[] args)
{
IFlyable[] flyingObjects = { new Bird(), new Airplane() };
foreach (var obj in flyingObjects)
{
obj.Fly();
}
}
}
В этом примере интерфейс IFlyable определяет метод Fly, который реализуют оба класса Bird и Airplane. Программа может работать с объектами, реализующими интерфейс, независимо от их конкретных типов.
Какие вы знаете модификаторы доступа?
В C# есть пять основных модификаторов доступа, которые определяют уровень видимости и доступности классов и членов классов (полей, методов, свойств и т.д.):
-
public - Доступен для всех. Члены, объявленные как
public, могут быть доступны из любого кода. -
private - Доступен только внутри класса или структуры. Это самый ограничительный уровень доступа.
-
protected - Доступен внутри класса и его производных классов. Используется для обеспечения доступа к членам только в рамках наследования.
-
internal - Доступен в пределах одной сборки. Члены с модификатором
internalмогут быть использованы в любом коде, который находится в той же сборке, но недоступны из других сборок. -
protected internal - Доступен в пределах одной сборки или производных классов. Это объединение модификаторов
protectedиinternal, дающее доступ либо в пределах одной сборки, либо в производных классах, даже если они находятся в другой сборке.
Дополнительно в C# 7.2 был введен модификатор private protected:
- private protected - Доступен внутри класса или структуры и
его производных классов, которые находятся в той же сборке. Это комбинация
модификаторов
privateиprotected.
В чем разница между обычным классом и статическим?
В C# есть несколько ключевых различий между обычным (нестатическим) классом и статическим классом:
-
Экземпляры:
-
Обычный класс: Может создавать экземпляры (объекты) с использованием оператора
new. -
Статический класс: Нельзя создавать экземпляры. Все члены класса должны быть статическими.
-
-
Члены класса:
-
Обычный класс: Может содержать как статические, так и нестатические члены (методы, поля, свойства, события).
-
Статический класс: Все члены класса должны быть статическими.
-
-
Наследование:
-
Обычный класс: Может наследоваться от других классов и может быть базовым классом для других классов.
-
Статический класс: Не может наследоваться от других классов и не может быть базовым классом.
-
-
Конструкторы:
-
Обычный класс: Может иметь как обычные, так и статические конструкторы.
-
Статический класс: Может иметь только статические конструкторы. Обычные конструкторы не допускаются.
-
В чем разница переопределения метода между ключевыми словами new и override?
В C# ключевые слова new и override используются для различных целей при работе с методами в производных классах:
-
new:-
Скрывает метод базового класса.
-
Используется, когда метод в производном классе имеет ту же сигнатуру, что и метод в базовом классе, но вы хотите, чтобы он не переопределял его, а скрывал.
-
Пример:
public class BaseClass { public void Method() { /* ... */ } } public class DerivedClass : BaseClass { public new void Method() { /* ... */ } }
-
-
override:-
Переопределяет виртуальный метод базового класса.
-
Используется для изменения поведения метода в производном классе, который был помечен как
virtual,abstractилиoverrideв базовом классе. -
Пример:
public class BaseClass { public virtual void Method() { /* ... */ } } public class DerivedClass : BaseClass { public override void Method() { /* ... */ } }
-
Таким образом, new используется для скрытия метода, а override — для переопределения метода.
Какое различие между const и readonly?
В C# ключевые слова const и readonly используются для создания неизменяемых значений, но они имеют разные применения и ограничения:
-
const:-
Компилируется как константа времени компиляции.
-
Должен быть инициализирован при объявлении.
-
По умолчанию является статическим (неявно static).
-
Можно использовать только с примитивными типами данных и строками.
-
Пример:
public const int ConstValue = 10;
-
-
readonly:-
Поле readonly присваивается при объявлении или в соответствующем конструкторе. Для ссылочного типа неизменяема ссылка, но сам объект может оставаться изменяемым.
-
Может быть инициализирован во время выполнения.
-
Может быть нестатическим (экземплярным) или статическим.
-
Можно использовать с любыми типами данных.
-
Пример:
public readonly int ReadOnlyValue; public MyClass(int value) { ReadOnlyValue = value; }
-
Таким образом, const используется для значений, известных на этапе компиляции, а readonly — для значений, которые могут быть установлены во время выполнения.
Разница между структурой и классом. Приведите примеры структур.
В C# структуры и классы имеют ключевые различия:
-
Тип данных:
-
Структуры (
struct): Значимые типы (хранят данные непосредственно). -
Классы (
class): Ссылочные типы (хранят ссылку на данные в памяти).
-
-
Наследование:
-
Структуры: Не поддерживают наследование, кроме реализации интерфейсов.
-
Классы: Поддерживают наследование и могут быть базовыми и производными классами.
-
-
Конструкторы:
-
Структуры могут иметь параметризованные конструкторы, а с C# 10 — и явный public-конструктор без параметров. default(T) по-прежнему обнуляет поля, не вызывая такой конструктор.
-
Классы: Могут иметь как параметризованные, так и непараметризованные конструкторы.
-
-
Инициализация:
-
Структуры: default(T) дает значения полей по умолчанию; new T() может вызвать явный конструктор. Локальную переменную нужно определенно присвоить до чтения.
-
Классы: Требуют явной инициализации.
-
Примеры структур:
public struct Point
{
public int X;
public int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
}
public struct Rectangle
{
public Point TopLeft;
public Point BottomRight;
public Rectangle(Point topLeft, Point bottomRight)
{
TopLeft = topLeft;
BottomRight = bottomRight;
}
}
Структуры часто используются для небольших, неизменяемых объектов, таких как координаты, временные отметки и т.д.
Может ли экземпляр структуры храниться в куче (heap)? Как это сделать?
ref передает ссылку на существующее хранилище и сам по себе не перемещает структуру в кучу. Пример boxing: Point p = new Point(1, 2); object boxed = p; — в boxed помещается копия значения.