При выполнении этого кода получается следующий результат. Следующее число равно 2 Следующее число равно 4 Следующее число равно 6 Следующее число равно 8 Следующее число равно 10 Начать с числа 21 Следующее число равно 23 Следующее число равно 25 Следующее число равно 27 Следующее число равно 29 Следующее число равно 31 Интерфейсные индексаторы
В интерфейсе можно также указывать индексаторы. Ниже приведена общая форма объявления интерфейсного индексатора. // Интерфейсный индексатор тип_элемента this[int индекс]{ get; set; }
Как и прежде, в объявлении интерфейсных индексаторов, доступных только для чтения или только для записи, должен присутствовать единственный аксессор: get или set соответственно.
Ниже в качестве примера приведен еще один вариант реализации интерфейса ISeries, в котором добавлен индексатор только для чтения, возвращающий i-й эле мент числового ряда. // Добавить индексатор в интерфейс. using System; public interface ISeries { // Интерфейсное свойство. int Next { get; // возвратить следующее по порядку число set; // установить следующее число } // Интерфейсный индексатор. int this[int index] { get; // возвратить указанное в ряду число } } // Реализовать интерфейс ISeries. class ByTwos : ISeries { int val; public ByTwos() { val = 0; } // Получить или установить значение с помощью свойства. public int Next { get { val += 2; return val; } set { val = value; } } // Получить значение по индексу. public int this[int index] { get { val = 0; for(int i=0; i < index; i++) val += 2; return val; } } } // Продемонстрировать применение интерфейсного индексатора. class SeriesDemo4 { static void Main() { ByTwos ob = new ByTwos(); // Получить доступ к последовательному ряду чисел с помощью свойства. for(int i=0; i < 5; i++) Console.WriteLine("Следующее число равно " + ob.Next); Console.WriteLine("\nНачать с числа 21"); ob.Next = 21; for (int i=0; i < 5; i++) Console.WriteLine("Следующее число равно " + ob.Next); Console.WriteLine("\nСбросить в 0"); ob.Next = 0; // Получить доступ к последовательному ряду чисел с помощью индексатора for(int i=0; i < 5; i++) Console.WriteLine("Следующее число равно " + ob[i]); } }
Вот к какому результату приводит выполнение этого кода. Следующее число равно 2 Следующее число равно 4 Следующее число равно 6 Следующее число равно 8 Следующее число равно 10 Начать с числа 21 Следующее число равно 23 Следующее число равно 25 Следующее число равно 27 Следующее число равно 29 Следующее число равно 31 Сбросить в 0 Следующее число равно 0 Следующее число равно 2 Следующее число равно 4 Следующее число равно 6 Следующее число равно 8 Наследование интерфейсов
Один интерфейс может наследовать другой. Синтаксис наследования интерфейсов такой же, как и у классов. Когда в классе реализуется один интерфейс, наследующий другой, в нем должны быть реализованы все члены, определенные в цепочке наследо вания интерфейсов, как в приведенном ниже примере. // Пример наследования интерфейсов. using System; public interface IA { void Meth1(); void Meth2(); } // В базовый интерфейс включены методы Meth1() и Meth2(). // а в производный интерфейс добавлен еще один метод — Meth3(). public interface IB : IA { void Meth3(); } // В этом классе должны быть реализованы все методы интерфейсов IA и IB. class MyClass : IB { public void Methl() { Console.WriteLine("Реализовать метод Meth1()."); } public void Meth2() { Console.WriteLine("Реализовать метод Meth2()."); } public void Meth3() { Console.WriteLine("Реализовать метод Meth3()."); } } class IFExtend { static void Main() { MyClass ob = new MyClass(); ob.Meth1(); ob.Meth2(); ob.Meth3(); } }
Ради интереса попробуйте удалить реализацию метода Meth1() из класса MyClass. Это приведет к ошибке во время компиляции. Как пояснялось ранее, в любом классе, реализующем интерфейс, должны быть реализованы все методы, определенные в этом интерфейсе, в том числе и те, что наследуются из других интерфейсов. Сокрытие имен при наследовании интерфейсов
Когда один интерфейс наследует другой, то в производном интерфейсе может быть объявлен член, скрывающий член с аналогичным именем в базовом интерфейсе. Такое сокрытие имен происходит в том случае, если член в производном интерфей се объявляется таким же образом, как и в базовом интерфейсе. Но если не указать в объявлении члена производного интерфейса ключевое слово new, то компилятор вы даст соответствующее предупреждающее сообщение. Явные реализации
При реализации члена интерфейса имеется возможность указать его имя полно стью вместе с именем самого интерфейса. В этом случае получается явная реализация члена интерфейса, или просто явная реализация. Так, если объявлен интерфейс IMyIF interface IMyIF { int MyMeth(int x); }
то следующая его реализация считается вполне допустимой: class MyClass : IMyIF { int IMyIF.MyMeth(int x) { return x / 3; } }
Как видите, при реализации члена MyMeth() интерфейса IMyIF указывается его полное имя, включающее в себя имя его интерфейса.
Для явной реализации интерфейсного метода могут быть две причины. Во-первых, когда интерфейсный метод реализуется с указанием его полного имени, то такой ме тод оказывается доступным не посредством объектов класса, реализующего данный интерфейс, а по интерфейсной ссылке. Следовательно, явная реализация позволяет реализовать интерфейсный метод таким образом, чтобы он не стал открытым членом класса, предоставляющего его реализацию. И во-вторых, в одном классе могут быть реализованы два интерфейса с методами, объявленными с одинаковыми именами и сигнатурами. Но неоднозначность в данном случае устраняется благодаря указанию в именах этих методов их соответствующих интерфейсов. Рассмотрим каждую из этих двух возможностей явной реализации на конкретных примерах.
В приведенном ниже примере программы демонстрируется интерфейс IEven, в котором объявляются два метода: IsEven() и IsOdd(). В первом из них определяет ся четность числа, а во втором — его нечетность. Интерфейс IEven затем реализуется в классе MyClass. При этом метод IsOdd() реализуется явно. // Реализовать член интерфейса явно. using System; interface IEven { bool IsOdd(int x); bool IsEven(int x); } class MyClass : IEven { // Явная реализация. Обратите внимание на то, что // этот член является закрытым по умолчанию. bool IEven.IsOdd(int x) { if((x%2) != 0) return true; else return false; } // Обычная реализация, public bool IsEven(int x) { IEven о = this; // Интерфейсная ссылка на вызывающий объект. return !о.IsOdd(х); } } class Demo { static void Main() { MyClass ob = new MyClass(); bool result; result = ob.IsEven(4); if(result) Console.WriteLine("4 - четное число."); // result = ob.IsOdd(4); // Ошибка, член IsOdd интерфейса IEven недоступен // Но следующий код написан верно, поскольку в нем сначала создается // интерфейсная ссылка типа IEven на объект класса MyClass, а затем по // этой ссылке вызывается метод IsOdd(). IEven iRef = (IEven) ob; result = iRef.IsOdd(3); if(result) Console.WriteLine("3 — нечетное число."); } }
В приведенном выше примере метод IsOdd() реализуется явно, а значит, он недо ступен как открытый член класса MyClass. Напротив, он доступен только по интер фейсной ссылке. Именно поэтому он вызывается посредством переменной о ссылоч ного типа IEven в реализации метода IsEven().
Ниже приведен пример программы, в которой реализуются два интерфейса, при чем в обоих интерфейсах объявляется метод Meth(). Благодаря явной реализации ис ключается неоднозначность, характерная для подобной ситуации. // Воспользоваться явной реализацией для устранения неоднозначности. using System; interface IMyIF_A { int Meth(int x); } interface IMyIF_B { int Meth(int x); } // Оба интерфейса реализуются в классе MyClass. class MyClass : IMyIF_A, IMyIF_B { // Реализовать оба метода Meth() явно. int IMyIF_A.Meth(int x) { return x + x; } int IMyIF_B.Meth(int x) { return x * x; } // Вызывать метод Meth() по интерфейсной ссылке. public int MethA(int x) { IMyIF_A a_ob; a_ob = this; return a_ob.Meth(x); // вызов интерфейсного метода IMyIF_A } public int MethB(int x){ IMyIF_B b_ob; b_ob = this; return b_ob.Meth(x); // вызов интерфейсного метода IMyIF_B } } class FQIFNames { static void Main() { MyClass ob = new MyClass(); Console.Write("Вызов метода IMyIF_A.Meth(): "); Console.WriteLine(ob.MethA(3)); Console.Write("Вызов метода IMyIF_B.Meth(): "); Console.WriteLine(ob.MethB(3)); } }
Вот к какому результату приводит выполнение этой программы. Вызов метода IMyIF_A.Meth(): 6 Вызов метода IMyIF_B.Meth(): 9
Анализируя приведенный выше пример программы, обратим прежде всего вни мание на одинаковую сигнатуру метода Meth() в обоих интерфейсах, IMyIF_A и IMyIF_B. Когда оба этих интерфейса реализуются в классе MyClass, для каждого из них в отдельности это делается явно, т.е. с указанием полного имени метода Meth(). А поскольку явно реализованный метод может вызываться только по интерфейсной ссылке, то в классе MyClass создаются две такие ссылки: одна — для интерфейса IMyIF_A, а другая — для интерфейса IMyIF_B. Именно по этим ссылкам происходит обращение к объектам данного класса с целью вызвать методы соответствующих ин терфейсов, благодаря чему и устраняется неоднозначность. Выбор между интерфейсом и абстрактным классом
Одна из самых больших трудностей программирования на C# состоит в правиль ном выборе между интерфейсом и абстрактным классом в тех случаях, когда требу ется описать функциональные возможности, но не реализацию. В подобных случаях рекомендуется придерживаться следующего общего правила: если какое-то понятие можно описать с точки зрения функционального назначения, не уточняя конкретные детали реализации, то следует использовать интерфейс. А если требуются некоторые детали реализации, то данное понятие следует представить абстрактным классом. Стандартные интерфейсы для среды .NET Framework
Для среды .NET Framework определено немало стандартных интерфейсов, которы ми можно пользоваться в программах на С#. Так, в интерфейсе System.IComparable определен метод CompareTo(), применяемый для сравнения объектов, когда требу ется соблюдать отношение порядка. Стандартные интерфейсы являются также важ ной частью классов коллекций, предоставляющих различные средства, в том числе стеки и очереди, для хранения целых групп объектов. Так, в интерфейсе System. Collections.ICollection определяются функции для всей коллекции, а в интер фейсе System.Collections.IEnumerator — способ последовательного обращения к элементам коллекции. Эти и многие другие интерфейсы подробнее рассматривают ся в части II данной книги. Структуры