KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программирование » Симон Робинсон - C# для профессионалов. Том II

Симон Робинсон - C# для профессионалов. Том II

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Симон Робинсон, "C# для профессионалов. Том II" бесплатно, без регистрации.
Перейти на страницу:

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()) {

 // код

} // 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, то код по-прежнему будет компилироваться, но компилятор будет выдавать предупреждение. Предупреждение служит для защиты от любых трудноуловимых ошибок, возникающих во время выполнения. Например, когда написана новая версия базового класса, в которой добавлен метод, имеющий, оказывается, такое же имя, как и существующий метод в производном классе.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*