В отличие от типов классов интерфейс может расширять множество базовых интерфейсов, что позволяет проектировать мощные и гибкие абстракции. Создайте новый проект консольного приложения по имени MiInterfaceHierarchy. Здесь имеется еще одна коллекция интерфейсов, которые моделируют разнообразные абстракции, связанные с визуализацией и фигурами. Обратите внимание, что интерфейс IShape расширяет и IDrawable, и IPrintable:
// IDrawable.cs
namespace MiInterfaceHierarchy
{
// Множественное наследование для интерфейсных типов - это нормально.
interface IDrawable
{
void Draw;
}
}
// IPrintable.cs
namespace MiInterfaceHierarchy
{
interface IPrintable
{
void Print;
void Draw; // < -- Здесь возможен конфликт имен!
}
}
// IShape.cs
namespace MiInterfaceHierarchy
{
// Множественное наследование интерфейсов. Нормально!
interface IShape : IDrawable, IPrintable
{
int GetNumberOfSides;
}
}
На рис. 8.6 показана текущая иерархия интерфейсов.
Главный вопрос: сколько методов должен реализовывать класс, поддерживающий IShape? Ответ: в зависимости от обстоятельств. Если вы хотите предоставить простую реализацию метода Draw, тогда вам необходимо реализовать только три члена, как иллюстрируется в следующем типе Rectangle:
using System;
namespace MiInterfaceHierarchy
{
class Rectangle : IShape
{
public int GetNumberOfSides => 4;
public void Draw => Console.WriteLine("Drawing...");
public void Print => Console.WriteLine("Printing...");
}
}
Если вы предпочитаете располагать специфическими реализациями для каждого метода Draw (что в данном случае имеет смысл), то конфликт имен можно устранить с использованием явной реализации интерфейсов, как делается в представленном далее типе Square:
namespace MiInterfaceHierarchy
{
class Square : IShape
{
// Использование явной реализации для устранения
// конфликта имен членов.
void IPrintable.Draw
{
// Вывести на принтер...
}
void IDrawable.Draw
{
// Вывести на экран...
}
public void Print
{
// Печатать...
}
public int GetNumberOfSides => 4;
}
}
В идеале к данному моменту вы должны лучше понимать процесс определения и реализации специальных интерфейсов с применением синтаксиса С#. По правде говоря, привыкание к программированию на основе интерфейсов может занять определенное время, так что если вы находитесь в некотором замешательстве, то это совершенно нормальная реакция.
Однако имейте в виду, что интерфейсы являются фундаментальным аспектом .NET Core. Независимо от типа разрабатываемого приложения (веб-приложение, настольное приложение с графическим пользовательским интерфейсом, библиотека доступа к данным и т.п.) работа с интерфейсами будет составной частью этого процесса. Подводя итог, запомните, что интерфейсы могут быть исключительно полезны в следующих ситуациях:
• существует единственная иерархия, в которой общее поведение поддерживается только подмножеством производных типов;
• необходимо моделировать общее поведение, которое встречается в нескольких иерархиях, не имеющих общего родительского класса кроме System.Object.
Итак, вы ознакомились со спецификой построения и реализации специальных интерфейсов. Остаток главы посвящен исследованию нескольких предопределенных интерфейсов, содержащихся в библиотеках базовых классов .NET Core. Как будет показано, вы можете реализовывать стандартные интерфейсы .NET Core в своих специальных типах, обеспечивая их бесшовную интеграцию с инфраструктурой.
Интерфейсы IEnumerable и IEnumerator
Прежде чем приступать к исследованию процесса реализации существующих интерфейсов .NET Core, давайте сначала рассмотрим роль интерфейсов IEnumerable и IEnumerator. Вспомните, что язык C# поддерживает ключевое слово foreach, которое позволяет осуществлять проход по содержимому массива любого типа:
// Итерация по массиву элементов.
int[] myArrayOfInts = {10, 20, 30, 40};
foreach(int i in myArrayOfInts)
{
Console.WriteLine(i);
}