Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
L10.doc
Скачиваний:
6
Добавлен:
06.09.2019
Размер:
65.54 Кб
Скачать

7

Лекция 10

5.2. Перегрузка членов

Перегрузка означает определение нескольких членов класса с одним и тем же именем, но с разной сигнатурой (описанием параметров). Перегрузку чаще всего применяют с методами. С# позволяет также перегружать операторы, обеспечивая таким образом нестандартное поведение пользовательских типов данных по отношению к операторам.

При вызове перегру­женного метода исполняющая среда проверяет типы переданных аргументов, срав­нивает их список с сигнатурами доступных перегруженных методов, и вы­зывает тот, чья сигнатура совпала с имеющимся списком. Если ни один из перегруженных методов не принимает указанный набор аргументов, генерируется ошибка.

Для создания перегруженных методов не существует специальных правил, кроме следующего: у перегруженного метода должно быть то же имя, что и у существующего метода, но другая сигнатура. Версии пере­груженного метода могут также отличаться уровнями доступа и типом возвраща­емого значения (но это не обязательно). Например:

public void DisplayMessage (int I)

{

MessageBox.Show(I.ToString( ));

}

public void DisplayMessage (string S)

{

MessageBox.Show(S);

}

Перегрузка операторов бывает полезной при разработке пользовательских типов данных, когда требуется определить арифме­тические, логические операторы или операторы сравнения, способные ра­ботать с этими типами. Рассмотрим следующую структуру:

public struct HoursWorked

{

float RegularHours, OvertimeHours;

}

Подобную структуру можно использовать в приложении для учета рабочего времени и сверхурочных. но при работе с несколькими экземплярами такой структуры возможны сложности. Представим, например, что требуется сложить значения двух экземпляров такой структуры. Чтобы решить эту задачу, придется писать новый метод, способный складывать экземпляры такой структуры. Для сложения трех и более экземпляров потребуется вызвать этот ме­тод несколько раз. С# позволяет определять поведение оператора при обработке пользовательских типов данных. Например, чтобы получить возможность суммировать структуру из примера, можно создать перегруженную версию оператора «+», добавив к ней необходимую функциональность.

Перегруженные операторы создаются подобно статическим методам с помощью ключевого слова operator, имеющего следующий синтаксис:

public static type operator op (Argument1[, Argument2])

{

// Здесь должна быть реализация

}

Здесь type – пользовательский тип, с которым работает оператор, который одновременно является и типом значения, возвращаемого оператором. Argument1 и Argument2 – аргументы опе­ратора. Любой унарный оператор обрабатывает только один аргумент с типом type. Бинарные операторы требуют два аргумента, хотя бы один из которых должен относиться к типу type. Op – это собственно оператор, например +, –, >, != и так далее. Перегруженные операторы необходимо объявлять с модификаторами public и static, внутри описания поль­зовательского типа данных (структуры или класса), для работы с которым он предназначен. Например:

public struct HoursWorked

{

float RegularHours, OvertimeHours;

public static HoursWorked operator + (HoursWorked a, HoursWorked b)

{

HoursWorked Result = new HoursWorked ();

Result.RegularHours = a.RegularHours + b.RegularHours;

Result.OvertimeHours = a.OvertimeHours + b.OvertimeHours;

return Result;

}

}

Перегруженный оператор можно использовать в программе как любой другой оператор. Например, можно «складывать» более одной переменной:

HoursWorked total = new HoursWorked ();

total = Sunday + Tuesday + Monday;

Как и в случае с перегруженными методами, разрешается создание различных реализаций перегруженных операторов при условии, что они отличаются сигнату­рами. Заметим, что в отличие от неуправляемого C++, перегрузка оператора присваивания в C# не допускается. Возможно, это связано с особой ролью оператора присваивания по отношению к целому виду членов – свойствам.

Резюме

  • Благодаря перегрузке можно создать несколько методов с одинаковым именем и разными реализациями. Перегруженные методы должны отличаться сигнатурами, но могут иметь одинаковые уровни доступа и типы возвращаемых зна­чений. Перегруженные методы объявляются так же, как и обычные.

  • В программах на С# можно определять нестандартные версии операторов, предназначенные для работы с пользовательскими типами данных. Перегруженные операторы объявляются с модификаторами public static и ключевым сло­вом operator.

5.3. Реализация полиморфизма посредством интерфейсов

Интерфейсы определяют внешнее поведение класса (или структуры). Интерфейс как аналог класса может содержать лишь 3 вида членов: методы, свойства и события. Кроме того, для методов в определении интерфейса указывается лишь описание типов параметров и возвращаемых значений, без реализации. Реализация членов, объяв­ленных в интерфейсе, целиком ложится на класс, в котором будет реализован этот ин­терфейс.

Таким образом, в разных объектах одни и те же члены интерфейса могут реализоваться по-разному. Рассмотрим интерфейс IShape, определяющий единственный метод – Calculate Area. При этом класс Circle, в котором реализован этот интерфейс, вычис­ляет площадь фигуры не так, как это делает класс Square, в котором также реализован этот интерфейс. Тем не менее, объект, которому необходимо использовать IShape, может единообразно вызывать метод CalculateArea из класса Circle или Square и получать корректный результат.

Интерфейсы определяются с помощью ключевого слова interface. Методы-члены интерфейса описываются с обычной сигнатурой метода, но без модификаторов доступа (public, private и других). Модификатор доступа, заданный для самого интерфейса, определяет уровень доступа для всех его чле­нов. Например:

public interface IDrivable

{

void GoForward (int Speed);

void Halt ();

int DistanceTraveled ();

int FuelLevel

{

get; set;

}

event System.EventHandler OutOfFuel;

}

Смысл методов get и set для свойств интерфейса состоит лишь в том, чтобы определить, может ли это свойство быть прочитанным или записанным. Так, если не указать set, то свойство будет «только для чтения».

Объект, в котором реализован некоторый интерфейс, предоставляет этот интерфейс для использования другими объектами. Например:

public void GoSomewhere (IDrivable v)

{

// Реализация опущена

}

Этому методу в качестве параметра можно передать любой объект, в котором реализован интерфейс IDrivable. Во время вызова передаваемый объект неявно преобразуется в тип соответствующего интерфейса. Кроме того, разрешается явно преобразовывать объекты, реализующие некоторый интерфейс, к типу этого интерфейса. Следующий пример демонстриру­ет преобразование объекта Truck к типу интерфейса IDrivable (чтобы это работало, в объекте Truck необходимо реализовать интерфейс IDrivable):

Truck myTruck = new Truck ();

IDrivable myVehicle;

myVehicle = (IDrivable) myTruck;

Чтобы в классе или структуре реализовать некоторый интерфейс, необходимо указать это в заголовке:

public class Truck: IDrivable

{

// Реализация опущена

}

В типе могут реализовываться несколько интерфейсов. Чтобы объявить такой тип, достаточно указать все необходимые интерфейсы через запятую:

public class Truck: IDrivable, IFuelBurning, ICargoCarrying

{

// Реализация опущена

}

Если в классе или структуре объявлена реализация некоторого интерфейса, то она должна быть полной. Для каждого члена интерфейса необходимо в реализующем классе (или структуре) определить член с тем же именем, причем этот член класса объявляется с уровнем доступа, с которым объявлен сам интерфейс. Например:

public interface IDrivable

{

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]