public int NumberOfSides { get; set; }
// Обратите внимание, что свойство Perimeter не реализовано.
}
}
Здесь мы невольно попали в первую "ловушку", связанную с использованием стандартных реализаций интерфейсов. Свойство Perimeter, определенное в интерфейсе IRegularPointy, в классе Square не определено, что делает его недоступным экземпляру класса Square. Чтобы удостовериться в этом, создайте новый экземпляр класса Square и выведите на консоль соответствующие значения:
Console.WriteLine("\n***** Fun with Interfaces *****\n");
...
var sq = new Square("Boxy")
{NumberOfSides = 4, SideLength = 4};
sq.Draw();
// Следующий код не скомпилируется:
// Console.WriteLine($"{sq.PetName} has {sq.NumberOfSides} of length
{sq.SideLength} and a perimeter of {sq.Perimeter}");
Взамен экземпляр Square потребуется явно привести к интерфейсу IRegularPointy (т.к. реализация находится именно там) и тогда можно будет получать доступ к свойству Perimeter. Модифицируйте код следующим образом:
Console.WriteLine($"{sq.PetName} has {sq.NumberOfSides} of length {sq.SideLength} and a
perimeter of {((IRegularPointy)sq).Perimeter}");
Один из способов обхода этой проблемы — всегда указывать интерфейс типа. Измените определение экземпляра Square, указав вместо типа Square тип IRegularPointy:
IRegularPointy sq = new Square("Boxy") {NumberOfSides = 4, SideLength = 4};
Проблема с таким подходом (в данном случае) связана с тем, что метод Draw() и свойство PetName в интерфейсе не определены, а потому на этапе компиляции возникнут ошибки.
Хотя пример тривиален, он демонстрирует одну из проблем, касающихся стандартных реализаций. Прежде чем задействовать это средство в своем коде, обязательно оцените последствия того, что вызывающему коду должно быть известно, где находятся реализации.
Статические конструкторы и члены (нововведение в версии 8.0)
Еще одним дополнением интерфейсов в C# 8.0 является возможность наличия в них статических конструкторов и членов, которые функционируют аналогично статическим членам в определениях классов, но определены в интерфейсах. Добавьте к интерфейсу IRegularPointy статическое свойство и статический конструктор:
interface IRegularPointy : IPointy
{
int SideLength { get; set; }
int NumberOfSides { get; set; }
int Perimeter => SideLength * NumberOfSides;
// Статические члены также разрешены в версии C# 8
static string ExampleProperty { get; set; }
static IRegularPointy() => ExampleProperty = "Foo";
}
Статические конструкторы не должны иметь параметры и могут получать доступ только к статическим свойствам и методам. Для обращения к статическому свойству интерфейса добавьте к операторам верхнего уровня следующий код:
Console.WriteLine($"Example property: {IRegularPointy.ExampleProperty}");
IRegularPointy.ExampleProperty = "Updated";
Console.WriteLine($"Example property: {IRegularPointy.ExampleProperty}");
Обратите внимание, что к статическому свойству необходимо обращаться через интерфейс, а не переменную экземпляра.
Использование интерфейсов в качестве параметров
Учитывая, что интерфейсы являются допустимыми типами, можно строить методы, которые принимают интерфейсы в качестве параметров, как было проиллюстрировано на примере метода CloneMe() ранее в главе. Предположим, что вы определили в текущем примере еще один интерфейс по имени IDraw3D:
namespace CustomInterfaces
{
// Моделирует способность визуализации типа в трехмерном виде.
public interface IDraw3D
{
void Draw3D();
}
}
Далее сконфигурируйте две из трех фигур (Circle и Hexagon) с целью поддержки нового поведения:
// Circle поддерживает IDraw3D.