Конструкторы по умолчанию
Как и в C++, классы C# обычно имеют конструктор по умолчанию без параметров, который просто вызывает конструктор без параметров непосредственного базового класса, а затем инициализирует все поля их параметрами по умолчанию. Так же как в C++, компилятор будет создавать этот конструктор по умолчанию, только если в коде явно не предоставлен никакой другой конструктор. Если какие-либо конструкторы присутствуют в определении класса, то в этом случае будут доступны только эти конструкторы, независимо от того, есть или нет среди них конструктор без параметров.
Как и в C++ можно обойтись без создания экземпляров класса, объявляя закрытый конструктор единственным.
class MyClass {
private MyClass() {
}
Это также не позволяет создавать экземпляры любых производных классов. Однако, если класс или методы в нем объявлены абстрактными, то нельзя создать экземпляр этого класса, причем не обязательно производного класса.
Списки инициализации конструктора
Конструкторы C# могут иметь элементы, которые выглядят как списки инициализации конструктора C++. Однако в C# такой список содержит только максимум один член и называется инициализатором конструктора. Элемент в инициализаторе должен быть либо конструктором непосредственного базового класса, либо другим конструктором того же класса. Синтаксис этих двух вариантов использует ключевые слова base
и this
соответственно:
class MyClass : MyBaseClass {
MyClass(int X)
: base(X) // выполняет конструктор MyBaseClass с одним параметром
{
// здесь другая инициализация
}
MyClass()
: this(10) // выполняет конструктор MyClass с одним параметром,
// передавая в него значение 10
{
// здесь другая инициализация
}
Если явно не задан никакой список инициализации конструктора, то компилятор будет неявно использовать список из элемента base()
. Другими словами, инициализатор по умолчанию вызывает конструктор по умолчанию базового класса. Это поведение совпадает с C++.
В отличие от C++ нельзя поместить переменные члены в список инициализации конструктора. Однако это только вопрос синтаксиса, так как эквивалент C# должен отметить свои начальные значения в определении класса.
Более серьезным различием является тот факт, что можно поместить только один иной конструктор в список. Это влияет на способ разработки конструкторов, хотя несомненно полезно, так как заставляет использовать хорошо определенную и эффективную парадигму организации конструкторов. Эта парадигма указана в приведенном выше коде. Все конструкторы следуют единому порядку, в котором выполняются различные конструкторы.
Деструкторы
C# реализует отличную от C++ модель программирования деструкторов. Это связано с тем, что механизм сборки мусора в C# предполагает следующее:
□ Существует меньшая необходимость в деструкторах, так как динамически распределенная память будет удаляться автоматически.
□ Так как невозможно предсказать, когда сборщик мусора реально разрушит заданный объект, то если для класса предоставляется деструктор, невозможно предсказать в точности, когда этот деструктор будет выполнен.
Поскольку память очищается в C# 'за сценой', то оказывается, что только небольшое число классов действительно нуждается в деструкторах. Для тех, кому это нужно (это классы, которые поддерживают внешние неуправляемые ресурсы, такие как соединения с файлами или с базами данных), C# имеет двухэтапный механизм разрушения:
1. Класс должен выводиться из интерфейса IDisposable
и реализовывать метод Dispose()
. Этот метод предназначен для явного вызова с помощью кода клиента для указания, что он закончил работать с объектом и требуется очистить ресурсы. (Интерфейсы будет рассмотрены позже в этом приложении.)
2. Класс должен отдельно реализовать деструктор, который рассматривается как запасной механизм, на случай, если клиент не вызывает Dispose()
.
Обычная реализация Dispose()
выглядит следующим образом:
public void Dispose() {
// очистка ресурсов
System.GC.SuppressFinalize(this);
}
System.GC
является базовым классом, представляющим сборщика мусора. SuppressFinalize()
является методом, который информирует сборщика мусора, что нет необходимости вызывать деструктор для разрушаемого объекта. Вызов SuppressFinalize()
важен, так как имеется снижение производительности, если в объекте есть деструктор, который нужно вызывать в то время, когда сборщик мусора выполняет свою работу. Следствием этого является то, что реальное освобождение управляемой памяти, занимаемой этим объектом, будет существенно задерживаться.
Синтаксис деструктора по сути такой же в C#, как и в C++. Отметим, что в C# не требуется объявлять деструктор виртуальным, компилятор будет это подразумевать. Не требуется также предоставлять модификатор доступа:
Class MyClass {
~MyClass() {
// очистка ресурсов
}
}
Хотя метод Dispose()
обычно явно вызывается клиентами, C# допускает альтернативный синтаксис, который гарантирует, что компилятор примет меры, чтобы он был вызван. Если переменная объявлена внутри блока using()
, то ее область действия совпадает с блоком using
и ее метод Dispose()
будет вызываться при выходе из блока:
using (MyClass MyObject = new MyClass()) {