
15.5. Реализация структурами интерфейсов
Применение одного базового класса с виртуальными членами позволяет единообразно обрабатывать объекты разных типов, каждый из которых является производным от базового и переопределяет его виртуальные члены.
Такой возможности для объектов разнотипных структур нет, т.к. структуры не исследуются. Однако структуры могут реализовывать интерфейсы. Если несколько структурных типов реализуют один и тот же интерфейс, то к объектам этих разных структур применимы методы (а также свойства, индексаторы и события) интерфейса. Тем самым интерфейсы позволяют одинаково обрабатывать объекты разных структурных типов. Для этого ссылка на интерфейс используется в качестве параметра, вместо которого могут подставляться аргументы структурных типов, реализующих данный интерфейс. Второй пример - применение коллекций (частный случай - массивов), элементы которых имеют разные структурные типы. Создание такого массива возможно, если он объявлен с типом интерфейса, реализованного всеми структурными типами.
В качестве примера определим интерфейс с прототипами свойств:
interface IShape
{
double Volume { get; } // объем
double Area { get; } // поверхность
}
Реализовать такой интерфейс можно с помощью разных классов и структур. Например, параллелепипед имеет площадь поверхности (Area) и объём (Volume). Те же свойства имеются у любой трёхмерной геометрической фигуры. Реализовать тот же интерфейс можно и в классе двумерных фигур. В этом случае объём будет равен нулю.
Определим статический метод с заголовком:
static void information (IShape sh)
Его назначение - вывести данные об объекте, ссылка на который используется в качестве аргумента. Тип параметра - это интерфейс, поэтому метод сможет обрабатывать объекты любых типов, реализующих интерфейс IShape.
В следующей программе определены две структуры, реализующие интерфейс IShape. Первая из них Circle - представляет круги с заданными значениями радиусов, вторая Sphere - сферы с заданными значениями радиуса. Метод information() выводит сведения об аргументе. Текст программы:
// 15_12.cs - структуры и интерфейсы
interface IShape
{
double Volume { get; } // объем
double Area { get; } // поверхность
}
struct Circle : IShape // Круг
{
public double radius;
public Circle(double radius) // конструктор
{ this.radius = radius; }
public double Area { get { return Math.PI * radius * radius; } }
public double Volume { get { return 0; } } // объем
}
struct Sphere : IShape // Сфера
{
public double radius;
public Sphere(double radius) // конструктор
{ this.radius = radius; }
public double Area // поверхность
{ get { return 4 * Math.PI * radius * radius; } }
public double Volume // объем
{ get { return 4 * Math.PI * radius * radius * radius / 3; } }
}
static void information(IShape sh)
{
Console.Write(sh.GetType());
Console.WriteLine(":\t Area={0,5:f2};\t Volume={1,5:f2}", sh.Area, sh.Volume);
}
public static void Main()
{
Circle ci = new Circle(25);
information(ci);
Sphere sp = new Sphere(4);
information(sp);
}
Результат выполнения программы: Circle: Area = 1963,50; Volume= 0,00 Sphere: Area = 201,06; Volume=268,08
В методе Main() созданы объекты структур Circle и Sphere, которые в качестве аргументов передаются методу information(). Обратите внимание на отсутствие приведения типов в теле метода information(). Если бы его параметр имел тип object, то необходимо было бы выполнять преобразование к типу конкретного аргумента.
Как и классы структура может реализовать одновременно несколько интерфейсов. Покажем на примере, как можно использовать эту возможность. Сначала объявим интерфейс такого вида:
interface IImage
{
void display();
double Measure { get; }
double BaseSize { set; }
}
Члены этого интерфейса могут быть реализованы по-разному, то есть им можно придать самый разный смысл. Пусть свойство Measure - это максимальный линейный размер («размах») геометрической фигуры; свойство BaseSize – базовый линейный размер; display() – прототип метода, который выводит сведения о типе, реализовавшем интерфейс, и значения свойств Measure, BaseSize конкретного объекта. Такими типами в нашем примере будут структуры Cube и Square, представляющие, соответственно, объекты «куб» и «квадрат». Для куба базовый размер – ребро куба, максимальный размер - наибольшая диагональ. Для квадрата базовый размер сторона квадрата, максимальный размер - его диагональ.
Предположим, что объекты этих структур мы хотим разместить в массиве и упорядочить элементы массива по убыванию значений свойства Measure. Для сортировки элементов массива можно применить метод Array.Sort(). Этот метод предполагает, что элементы массива сравниваются друг с другом с помощью метода CompareTo(). Прототип этого метода размещён в интерфейсе IComparable из пространства имён System. Метод CompareTo() уже определён для таких типов как int, char, string и т.д.. Однако для пользовательских типов, которыми будут структуры Cube и Square, этот метод нужно определять явно. Поэтому реализуем в указанных структурах интерфейс IComparable. Темсамым в каждой из этих структур с необходимостью появится такой нестатический метод:
public int CompareTo(object obj)
{
if (Measure < ((IImage)obj).Measure)
return +1;
if (Measure == ((IImage)obj).Measure)
return 0;
else
return -1;
}Обратите внимание, что тип object параметра obj приводится к типу интерфейса Ilmage, который должны иметь элементы массива.
Напомним, что в коде метода Array.Sort() выполняются многократные обращения к методу CompareTo(), где сравниваются характеристики двух элементов сортируемого массива. Если характеристика (в нашем примере свойство Measure) вызывающего элемента-объекта находится в «правильном» отношении к характеристике объекта-параметра, то метод CompareTo() должен возвращать отрицательное значение. При нарушении «порядка» элементов возвращается положительное значение. Для элементов, одинаковых по характеристике сравнения, возвращается значение 0.
Программа с указанными структурами может быть такой:
// 15_13.cs - структуры и интерфейсы
interface IImage
{
void display();
double Measure { get; }
double BaseSize { set; }
}
struct Cube : IImage, IComparable // куб
{
double rib; // ребро - базовый размер
public double Measure // максимальный линейный размер
{ get { return Math.Sqrt(3 * rib * rib); } }
public double BaseSize { set { rib = value; } }
public void display()
{
string form = "Размеры куба: peбpo={0,7:f3}; paзMax={1,7:f3}";
Console.WriteLine(form, rib, Measure);
}
public int CompareTo(object obj)
{
if (Measure < ((IImage)obj).Measure)
return +1;
if (Measure == ((IImage)obj).Measure)
return 0;
else
return -1;
}
}
struct Square : IImage, IComparable // квадрат
{
double side; // сторона - базовый размер
public double Measure // максимальный линейный размер
{ get { return Math.Sqrt(2 * side * side); } }
public void display()
{
string form = "Размеры квадрата: сторона = {0,7:f3}; размах={1,7:f3}";
Console.WriteLine(form, side, Measure);
}
public double BaseSize { set { side = value; } }
public int CompareTo(object obj)
{
if (Measure < ((IImage)obj).Measure)
return +1;
if (Measure == ((IImage)obj).Measure)
return 0;
else
return -1;
}
}
public static void Main()
{
Cube cube = new Cube();
cube.BaseSize = 5;
Square sq = new Square();
sq.BaseSize = 5;
Cube cube1 = new Cube();
cube1.BaseSize = 7;
IImage[] arlm = new IImage[] { cube, sq, cube1 };
Array.Sort(arlm);
foreach (IImage memb in arlm)
memb.display();
}
Результаты выполнения программы: Размеры куба: ребро= 7,000; разMах= 12,124 Размеры куба: ребро= 5,000; разMах= 8,660 Размеры квадрата: сторона= 5,000; разMах= 7,071
В методе Main() определены два экземпляра структуры Cube и один экземпляр структуры Square. С помощью свойства BaseSize заданы значения базовых размеров структур. Объявлен и инициализирован массив типа IImage[]. Ссылка на него arlm использована в качестве аргумента метода Array.Sort(). Цикл foreach перебора элементов коллекции (в нашем примере массива) последовательно обращается через ссылку memb типа Ilmage ко всем элементам упорядоченного массива. Для каждого элемента вызывается метод display().