Поскольку память очищается в 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) {
// код
} // MyObject.Dispose будет неявно вызван при выходе из этого блока
Отметим, что приведенный выше код будет компилироваться успешно только если
MyClass
выводится из интерфейса
IDisposable
и реализует метод
Dispose
. Если нежелательно использовать синтаксис
using
, то можно опустить один или оба из двух шагов в последовательности деструктора (реализация
Dispose
и реализация деструктора), но обычно реализуются оба шага. Можно также реализовать
Dispose
, без привлечения интерфейса
IDisposable
, но если это делается, то снова невозможно использовать синтаксис
using
, чтобы для экземпляров этого класса
Dispose
вызывался автоматически.
Наследование
Наследование работает в основном таким же образом в C#, как и в C++, с тем исключением, что множественная реализация наследования не поддерживается. Компания Microsoft считает, что множественное наследование ведет к коду, который хуже структурирован и который труднее сопровождать, и поэтому решила исключить это свойство из C#.
Class MyClass : MyBaseClass {
// и т.д.
В C++ указатель на класс может дополнительно указывать на экземпляр производного класса. (Виртуальные функции в конце концов зависят от этого факта.) В C# классы доступны через ссылки, но правило остается тем же. Ссылка на класс может ссылаться на экземпляры этого класса или на экземпляры любого производного класса.
MyBaseClass Mine;
Mine = new MyClass; //
все нормально, если MyClass будет производным
// от MyBaseClass
Если желательно, чтобы ссылка ссылалась на произвольный объект (эквивалент
void*
в C++), можно определить ее как
object
в C#, так как C# отображает
object
в класс
System.Object
, из которого выводятся все другие классы.
object Mine2 = new MyClass;
Виртуальные и невиртуальные функции
Виртуальные функции поддерживаются в C# таким же образом, как и в C++. Однако в C# существуют некоторые синтаксические отличия, которые созданы, чтобы исключить возможную неоднозначность в C++. Это означает, что некоторые типы ошибок, которые появляются в C++ только во время выполнения, будут идентифицированы в C# во время компиляции.
Отметим также, что в C# классы всегда доступны по ссылке (что эквивалентно доступу через указатель в C++).
Если в C++ требуется, чтобы функция была виртуальной необходимо просто определить ключевое слово
virtual
в базовом и производном классах. В противоположность этому в C# необходимо объявить функцию как
virtual
в базовом классе и как
override
в версиях производных классов.
class MyBaseClass {
public virtual void DoSomething(int X) {
// и т.д.
}
// и т.д.
}
class MyClass : MyBaseClass {
public override void DoSomething(int X) {
// и т.д.
}
// и т.д.
}
Важный момент этого синтаксиса состоит в том, что он дает понять компилятору, как интерпретировать функцию, и значит, исключается риск таких ошибок, где, например, вводится слегка неправильная сигнатура метода в переопределяемой версии, и поэтому определяется новая функция вместо переопределения существующей. Компилятор будет отмечать ошибку, если функция помечена как
override
, и компилятор не сможет идентифицировать ее версию в каком-либо базовом классе.
Если функция не является виртуальной, то можно все равно определить версии этого метода в производном классе, в этом случае говорят, что версия производного класса скрывает версию базового класса, а вызываемый метод зависит только от типа ссылки, используемой для доступа к классу, так же как это зависит от типа указателя, используемого для доступа к классу в C++.
В случае, если в C# версия функции в производном классе скрывает соответствующую функцию в базовом классе, можно явно указать это с помощью ключевого слова
new
.
class MyBaseClass {
public void DoSomething(int X) {
// и т.д.
}
// и т.д.
}
class MyClass : MyBaseClass {
public new void DoSomething(int X) {
// и т.д.
} и т.д.
}
Если не пометить новую версию класса явно как
new
, то код по-прежнему будет компилироваться, но компилятор будет выдавать предупреждение. Предупреждение служит для защиты от любых трудноуловимых ошибок, возникающих во время выполнения. Например, когда написана новая версия базового класса, в которой добавлен метод, имеющий, оказывается, такое же имя, как и существующий метод в производном классе.