class ThreeDCircle : Circle, IDraw3D
{
...
public void Draw3D()
=> Console.WriteLine("Drawing Circle in 3D!"); }
}
// Hexagon поддерживает IPointy и IDraw3D.
class Hexagon : Shape, IPointy, IDraw3D
{
...
public void Draw3D()
=> Console.WriteLine("Drawing Hexagon in 3D!");
}
На рис. 8.2 показана обновленная диаграмма классов в Visual Studio.
Теперь если вы определите метод, принимающий интерфейс IDraw3D в качестве параметра, тогда ему можно будет передавать по существу любой объект, реализующий IDraw3D. Попытка передачи типа, не поддерживающего необходимый интерфейс, приводит ошибке на этапе компиляции. Взгляните на следующий метод, определенный в классе Program:
// Будет рисовать любую фигуру, поддерживающую IDraw3D.
static void DrawIn3D(IDraw3D itf3d)
{
Console.WriteLine("-> Drawing IDraw3D compatible type");
itf3d.Draw3D();
}
Далее вы можете проверить, поддерживает ли элемент в массиве Shape новый интерфейс, и если поддерживает, то передать его методу DrawIn3D() на обработку:
Console.WriteLine("***** Fun with Interfaces *****\n");
Shape[] myShapes = { new Hexagon(), new Circle(),
new Triangle("Joe"), new Circle("JoJo") } ;
for(int i = 0; i < myShapes.Length; i++)
{
// Can I draw you in 3D?
if (myShapes[i] is IDraw3D s)
{
DrawIn3D(s);
}
}
Ниже представлен вывод, полученный из модифицированной версии приложения. Обратите внимание, что в трехмерном виде отображается только объект Hexagon, т.к. все остальные члены массива Shape не реализуют интерфейс IDraw3D:
***** Fun with Interfaces *****
...
-> Drawing IDraw3D compatible type
Drawing Hexagon in 3D!
Использование интерфейсов в качестве возвращаемых значений
Интерфейсы могут также применяться в качестве типов возвращаемых значений методов. Например, можно было бы написать метод, который получает массив объектов Shape и возвращает ссылку на первый элемент, поддерживающий IPointy:
// Этот метод возвращает первый объект в массиве,
// который реализует интерфейс IPointy.
static IPointy FindFirstPointyShape(Shape[] shapes)
{
foreach (Shape s in shapes)
{
if (s is IPointy ip)
{
return ip;
}
}
return null;
}
Взаимодействовать с методом FindFirstPointyShape() можно так:
Console.WriteLine("***** Fun with Interfaces *****\n");
// Создать массив элементов Shape.
Shape[] myShapes = { new Hexagon(), new Circle(),
new Triangle("Joe"), new Circle("JoJo")};
// Получитгь первый элемент, имеющий вершины.
IPointy firstPointyItem = FindFirstPointyShape(myShapes);
// В целях безопасности использовать null-условную операцию.
Console.WriteLine("The item has {0} points", firstPointyItem?.Points);
Массивы интерфейсных типов
Вспомните, что один интерфейс может быть реализован множеством типов, даже если они не находятся внутри той же самой иерархии классов и не имеют общего родительского класса помимо System.Object. Это позволяет формировать очень мощные программные конструкции. Например, пусть в текущем проекте разработаны три новых класса: два класса (Knife (нож) и Fork (вилка)) моделируют кухонные приборы, а третий (PitchFork (вилы)) — садовый инструмент. Ниже показан соответствующий код, а на рис. 8.3 — обновленная диаграмма классов.
// Fork.cs
namespace CustomInterfaces
{
class Fork : IPointy
{
public byte Points => 4;
}
}
// PitchFork.cs
namespace CustomInterfaces
{
class PitchFork : IPointy
{
public byte Points => 3;
}
}