Помимо указания базовых классов, мы можем таким же способом – через двоеточие – указать и поддерживаемые интерфейсы. Если одновременно требуется задавать базовый класс, то он должен располагаться первым после двоеточия, а интерфейсы – за ним. Если базовый класс не задается, то интерфейсы указываются непосредственно за двоеточием. Для отделения имени базового класса (если таковое присутствует) и для отделения одних имен интерфейсов от других следует использовать запятую.
Например, мы можем добавить в класс MyClass интерфейс:
public class MyClass : IMyInterface { // члены класса }
Все члены, являющиеся интерфейсами, должны иметь реализацию в любом классе, поддерживающем данный интерфейс, хотя, конечно, всегда существует возможность реализовать "пустой" интерфейс (без какого бы то ни было функционального кода), если от данного члена не требуется выполнения каких-либо действий.
Следующее объявление неверно, поскольку базовый класс MyBase не является первым вхождением списка наследования:
public class MyClass: IMyInterface, MyBase {
// члены класса }
Правильный способ задать и базовый класс, и интерфейс следующий:
public class MyClass : MyBase, IMyInterface { // члены класса }
Не забывайте, что допускается наличие нескольких интерфейсов, поэтому следующий код также является допустимым:
public class МуСlass: MyBase, IMyInterface, IMySecondInterface { // члены класса }
Ниже приводится таблица допустимых в определениях классов комбинаций модификаторов доступа.
Модификатор | Значение | |
Отсутствует internal | либо | Класс доступен только в рамках текущего проекта |
public | Класс доступен отовсюду | |
abstract или abstract | internal | Класс доступен только в рамках текущего проекта, не допускает создания экземпляров, может только наследоваться |
publicabstract | Класс доступен отовсюду, не допускает создания экземпляров, может только наследоваться | |
sealedилиinternalsealed | Класс доступен только в рамках текущего проекта, не может наследоваться, допускает только создание экземпляров | |
publicsealed | Класс доступен отовсюду, не может наследоваться, допускает только создание экземпляров |
Поскольку все классы наследуются от System.Object, то они обладают доступом ко всем его защищенным и общим членам. Поэтому имеет смысл ознакомиться с тем, что нам доступно System.Object содержит следующие методы:
Метод | Возвращаемый тип | Виртуальн ый | Статиче -ский | Описание |
Object() | Отсутствует | Нет | Нет | Конструктор типаSystem.Object.Автоматически вызывается конструкторамипроизводных типов |
-Object() (также известен под именем Finalize()) | Отсутствует | Нет | Нет | Деструктор для типаSystem.ObjectАвтоматически вызывается деструкторамипроизводных типов, сам по себе вызванбыть не может |
Equals (object) | bool | Да | Нет | Сравнивает объект, для котороговызывается, с другим объектом и возвращает значение true, если они равны. Реализация, выполняющаяся по умолчанию, проверяет, ссылается ли переданный в качестве параметра объект на тот же самый объект (поскольку объекты представляют собой ссылочные типы). Если необходимо сравнивать объекты каким-либо иным образом, например, на предмет одинакового значения, этот метод может быть переопределен. |
Equals (object, object) | bool | Нет | Да | Сравнивает два объекта, передаваемых ему в качестве параметров, на предмет того, равны ли они. Эта проверка выполняется. С помощью метода Equals (object). Заметьте, что, если оба объекта обладают нулевыми ссылками, возвращается значениеtrue. |
ReferenceEq uals(object, object) | bool | Нет | Да | Сравнивает два переданных ему объекта, определяя, являются ли они ссылками на один и тот же экземпляр. |
ToString() | string | Да | Нет | Возвращает строку, соответствующую экземпляру объекта. По умолчанию это квалифицированное имя класса (см предыдущий пример), однако метод можно переопределить для выполнения действий, подходящих для типа данного класса. |
Memberwise Clone() | object | Нет | Нет | Копирует объект посредством создания нового экземпляра объекта и копирования всех членов. Обратите внимание, что такое копирование членов не приводит к созданию новых экземпляров этих членов. Любые члены ссылочного типа в новом объекте будут ссылаться на те же объекты, на которые они ссылаются в исходном классе. Рассматриваемый метод является защищенным, поэтому его можно использовать внутри класса или внутри производных классов. |
GetType() | System.Type | Нет | Нет | Возвращает тип объекта в виде объектаSystem.Type. |
GetHashCod e() | int | Да | Нет | Используется как функция хеширования для объектов. Хеш-функция – это функция, возвращающая значение, которое позволяет идентифицировать объект в некоторой сжатой форме. |
Эти методы являются основными, они должны поддерживаться всеми типами обектов в .NET Framework, хотя, возможно, некоторые из них вам никогда не придется использовать (или только в определенных обстоятельствах, как, например, GetHashCode()).
Метод GetType () полезен при использовании полиморфизма, поскольку он позволяет выполнять разные коды для объектов разных типов, а не один и тот же код для всех объектов, как это часто бывает. Например, если у нас имеется функция, которой передается параметр типа object (это означает, что мы можем передавать ей практически все, что угодно), то можно предусмотреть выполнение в ней специальных работ в случае поступления объектов конкретного типа. Воспользовавшись сочетанием Getrype() с typeof() (оператор С#, который преобразовывает имя класса в объект System.Type), мы получаем возможность выполнять сравнения примерно следующим образом:
if (typeof(myObj) = = typeof(MyComplexClass)) {
// Объект myObj является экземпляром класса MyComplexClass }
Возвращаемый объект system.туре обладает гораздо более широкими возможностями, но здесь мы не будем на нем останавливаться.
Очень часто оказывается весьма полезным переопределить метод ToString (), особенно в тех ситуациях, когда содержимое объекта может быть с легкостью представлено в виде одной удобочитаемой строки.
6.2.Конструкторы и деструкторы
Когда мы описываем какой-либо класс в С#, то в большинстве случаев нет необходимости описывать соответствующие ему конструкторы и деструкторы, поскольку объект базового класса System.Object обеспечивает их реализацию по умолчанию. Однако иногда стоит описать их самостоятельно, что позволит инициализировать и уничтожать объекты.
Для того чтобы добавить в класс простой конструктор, необходимо воспользоваться следующим несложным синтаксисом:
class MyClass { public MyClass()
{
// Код конструктора }}
Такой конструктор обладает тем же именем, что и класс, в котором он содержится, не имеет параметров (что превращает его в конструктор, использующийся по умолчанию) и является общим, что позволяет создавать экземпляры объектов данного класса (более подробная информация по этому вопросу содержится в предыдущей лекции).
Существует также возможность использовать частный конструктор по умолчанию, что означает, что экземпляры объектов данного класса не могут создаваться с помощью этого конструктора (еще раз отсылаем читателей к предыдущей лекции).
class MyClass { private MyClass()
{
// Код конструктора
}
}
Кроме того, у нас имеется возможность аналогичным образом определить конструкторы данного класса, использующиеся не по умолчанию; для этого достаточно просто задать параметры. Например:
class MyClass
{
public MyClass () {
// Код конструктора, использующегося по умолчанию } public MyClass (int myInt) {
// Код конструктора, использующегося не по
// умолчанию (используется параметр myInt) } } Количество задаваемых конструкторов не ограничено.
Деструкторы определяются с помощью несколько иного синтаксиса. Деструктор, используемый в .NET (и предоставляемый классом System.Object), называется Finalize(), однако для объявления деструктора используется другое имя. Вместо того чтобы переопределять Finalize(), мы используем следующий код:
class MyClass {
~MyClass()
{
// тело деструктора } }
Код, заключенный в деструкторе, будет выполняться при сборке мусора, позволяя освобождать удерживаемые ресурсы. После вызова деструктора происходят явные вызовы деструкторов базовых классов, включая вызов Finalize() в корневом классе System.Object. Такой способ позволяет .NET Framework гарантировать выполнение, поскольку переопределение Finaiize() означало бы, что необходимы явные вызовы базовых классов, а это таит в себе потенциальную опасность (мы познакомимся с вызовом методов базовых классов в следующей лекции).