Интерфейсы
Фактически это те же самые абстрактные классы, НЕ СОДЕРЖАЩИЕ объявлений данных-членов и объявлений ОБЫЧНЫХ функций.
Все без исключения функции — члены интерфейса – абстрактные. Поэтому интерфейс объявляется с особым ключевым словом interface, а функции интерфейса, несмотря на свою "абстрактность", объявляются без ключевого слова abstract.
Основное отличие интерфейса от абстрактного класса заключается в том, что производный класс может наследовать одновременно несколько интерфейсов.
Объявление интерфейса
using System; namespace Interface01 { // Интерфейсы. // Этот интерфейс характеризуется уникальными // именами объявленных в нем методов. interface Ix { void IxF0(int xKey); void IxF1(); } // Пара интерфейсов, содержащих объявления одноименных методов // с одной и той же сигнатурой. interface Iy { void F0(int xKey); void F1(); } interface Iz { void F0(int xKey); void F1(); } // А этому интерфейсу уделим особое внимание. // Он содержит тот же набор методов, но в производном классе этот // интерфейс будет реализован явным образом. interface Iw { void F0(int xKey); void F1(); } // В классе TestClass наследуются интерфейсы... class TestClass:Ix ,Iy ,Iz ,Iw { public int xVal; // Конструкторы. public TestClass() { xVal = 125; } public TestClass(int key) { xVal = key; } // Реализация функций интерфейса Ix. // Этот интерфейс имеет специфические названия функций. // В данном пространстве имен его реализация неявная и однозначная. public void IxF0(int key) { xVal = key*5; Console.WriteLine("IxF0({0})...", xVal); } public void IxF1() { xVal = xVal*5; Console.WriteLine("IxF1({0})...", xVal); } // Реализация интерфейсов Iy и Iz в классе TestClass неразличима. // Это неявная неоднозначная реализация интерфейсов. // Однако неважно, чью конкретно функцию реализуем. // Оба интерфейса довольны... public void F0(int xKey) { xVal = (int)xKey/5; Console.WriteLine("(Iy/Iz)F0({0})...", xVal); } public void F1() { xVal = xVal/5; Console.WriteLine("(Iy/Iz)F1({0})...", xVal); } // А это явная непосредственная реализация интерфейса Iw. // Таким образом, класс TestClass содержит ТРИ варианта // реализации функций интерфейсов с одной и той же сигнатутой. // Два варианта реализации неразличимы. // Третий (фактически второй) вариант реализации // отличается квалифицированными именами. void Iw.F0(int xKey) { xVal = xKey+5; Console.WriteLine("Iw.F0({0})...", xVal); } void Iw.F1() { xVal = xVal–5; Console.WriteLine("Iw.F1({0})...", xVal); } public void bF0() { Console.WriteLine("bF0()..."); } } class Class1 { static void Main(string[] args) { TestClass x0 = new TestClass(); TestClass x1 = new TestClass(5); x0.bF0(); // Эти методы представляют собой неявную ОДНОЗНАЧНУЮ реализацию // интерфейса Ix. x0.IxF0(10); x1.IxF1(); // Эти методы представляют собой неявную НЕОДНОЗНАЧНУЮ реализацию // интерфейсов Iy и Iz. x0.F0(5); x1.F1(); // А вот вызов функций с явным приведением к типу интерфейса. // Собственный метод класса bF0() при подобных преобразованиях // не виден. (x0 as Iy).F0(7); (x1 as Iz).F1(); // А теперь настраиваем ссылки различных типов интерфейсов // на ОДИН И ТОТ ЖЕ объект – представитель класса TestClass. // И через "призму" интерфейса всякий раз объект будет // выглядеть по-разному. Console.WriteLine("==========Prism test=========="); Console.WriteLine("==========Ix=========="); Ix ix = x1; ix.IxF0(5); ix.IxF1(); Console.WriteLine("==========Iy=========="); Iy iy = x1; iy.F0(5); iy.F1(); Console.WriteLine("==========Iz=========="); Iz iz = x1; iz.F0(5); iz.F1(); Console.WriteLine("==========Iw=========="); Iw iw = x1; iw.F0(10); iw.F1(); } } }Листинг 8.1.
Преимущества программирования с использованием интерфейсов проявляются в том случае, когда ОДНИ И ТЕ ЖЕ ИНТЕРФЕЙСЫ наследуются РАЗНЫМИ классами. При этом имеет место еще один вариант полиморфности: объект, представленный ссылкой-интерфейсом, способен проявлять различную функциональность в зависимости от реализации функций наследуемого интерфейса в классе данного объекта. И здесь все определяется спецификой данной конкретной реализации:
using System; namespace Interface02 { // Объявляются два интерфейса, каждый из которых содержит объявление // одноименного метода с единственным параметром соответствующего типа. interface ICompare0 { bool Eq(ICompare0 obj); } interface ICompare1 { bool Eq(ICompare1 obj); } // Объявляются классы, наследующие оба интерфейса. // В каждом из классов реализуются функции интерфейсов. // В силу того, что объявленные в интерфейсах методы – одноименные, // в классах применяется явная реализация методов интерфейсов. // Для классов-наследников интерфейсы представляют собой всего лишь // базовые классы. Поэтому в соответствующих функциях сравнения // допускается использование операции as. //____________________________________________________________. class C0:ICompare0,ICompare1 { public int commonVal; int valC0; public C0(int commonKey, int key) { commonVal = commonKey; valC0 = key; } // Метод реализуется для обеспечения сравнения объектов // СТРОГО одного типа – C0. bool ICompare0.Eq(ICompare0 obj) { C0 test = obj as C0; if (test == null) return false; if (this.valC0 == test.valC0) return true; else return false; } // Метод реализуется для обеспечения сравнения объектов разного типа. bool ICompare1.Eq(ICompare1 obj) { C1 test = obj as C1; if (test == null) return false; if (this.commonVal == test.commonVal) return true; else return false; } } //____________________________________________________________. class C1:ICompare0,ICompare1 { public int commonVal; string valC1; public C1(int commonKey, string key) { commonVal = commonKey; valC1 = string.Copy(key); } // В классе C1 при реализации функции интерфейса ICompare0 реализован // метод сравнения, который обеспечивает сравнение как объектов типа C1, // так и объектов типа C0. bool ICompare0.Eq(ICompare0 obj) { C1 test; // Попытка приведения аргумента к типу C1. // В случае успеха – сравнение объектов по специфическому признаку, // который для данного класса представлен строковой переменной valC1. // В случае неуспеха приведения // (очевидно, что сравниваются объекты разного типа) // предпринимается попытка по второму сценарию // (сравнение объектов разных типов). // Таким образом, в рамках метода, реализующего один интерфейс, // используется метод второго интерфейса. Разумеется, при явном // приведении аргумента к типу второго интерфейса. test = obj as C1; if (test == null) return ((ICompare1)this).Eq((ICompare1)obj); if (this.valC1.Equals(test.valC1)) return true; else return false; } // Метод реализуется для обеспечения сравнения объектов разного типа. bool ICompare1.Eq(ICompare1 obj) { C0 test = obj as C0; if (test == null) return false; if (this.commonVal == test.commonVal) return true; else return false; } } //=============================================================== // Место, где порождаются и сравниваются объекты. class Class1 { static void Main(string[] args) { C0 x1 = new C0(0,1); C0 x2 = new C0(1,1); // В выражениях вызова функций - членов интерфейсов НЕ ТРЕБУЕТСЯ // явного приведения значения параметра к типу интерфейса. // Сравнение объектов - представителей одного класса (C0). if ((x1 as ICompare0).Eq(x2)) Console.WriteLine("Yes!"); else Console.WriteLine("No!"); C1 y1 = new C1(0,"1"); C1 y2 = new C1(1,"1"); // Сравнение объектов - представителей одного класса (C1). if ((y1 as ICompare0).Eq(y2)) Console.WriteLine("Yes!"); else Console.WriteLine("No!"); // Попытка сравнения объектов - представителей разных классов. if (((ICompare0)x1).Eq(y2)) Console.WriteLine("Yes!"); else Console.WriteLine("No!"); if ((x1 as ICompare1).Eq(y2)) Console.WriteLine("Yes!"); else Console.WriteLine("No!"); if (((ICompare1)y2).Eq(x2)) Console.WriteLine("Yes!"); else Console.WriteLine("No!"); // Здесь будет задействован метод сравнения, реализованный // в классе C0 по интерфейсу ICompare0, который не является универсальным. // Отрицательный результат может быть получен не только // по причине неравенства значений сравниваемых величин, // но и по причине несоответствия типов операндов. Console.WriteLine("__________x2==y2__________"); if ((x2 as ICompare0).Eq(y2)) Console.WriteLine("Yes!"); else Console.WriteLine("No!"); // А здесь вероятность положительного результата выше, поскольку в классе // C1 метод интерфейса ICompare0 реализован как УНИВЕРСАЛЬНЫЙ. // И это значит, // что данный метод никогда не вернет отрицательного значения по причине // несоответствия типов операндов. Console.WriteLine("__________y2==x2__________"); if ((y2 as ICompare0).Eq(x2)) Console.WriteLine("Yes!"); else Console.WriteLine("No!"); } } }Листинг 8.2.
Реализация сортировки в массиве. Интерфейс IComparable
Независимо от типа образующих массив элементов, массив элементов данного типа – это особый класс, производный от базового класса Array.
Класс Array является основой для любого массива и для любого массива предоставляет стандартный набор методов для создания, манипулирования (преобразования), поиска и сортировки на множестве элементов массива.
В частности, варианты метода Sort() обеспечивают реализацию механизмов сортировки элементов ОДНОМЕРНОГО массива объектов (в смысле представителей класса Object ).
Сортировка элементов предполагает:
- ПЕРЕБОР всего множества (или его части) элементов массива;
- СРАВНЕНИЕ (значений) элементов массива в соответствии с определенным критерием сравнения.
При этом критерии для сравнения и алгоритмы сравнения могут задаваться либо с помощью массивов ключей, либо реализацией интерфейса IComparable, который специфицирует характеристики (тип возвращаемого значения, имя метода и список параметров) методов, обеспечивающих реализацию данного алгоритма. Далее будут рассмотрены различные варианты метода Sort.
- Вариант метода Sort сортирует полное множество элементов одноразмерного массива с использованием алгоритма, который реализуется методами, специфицированными стандартным интерфейсом сравнения:
public static void Sort(Array);
Реализованный на основе интерфейса алгоритм способен распознавать значения элементов массива и сравнивать эти элементы между собой, если это элементы предопределенных типов.
Таким образом, никаких проблем не существует, если надо сравнить и переупорядочить массивы элементов, для которых известно, КАК СРАВНИВАТЬ значения составляющих массив элементов. Относительно ..., System.Int16, System.Int32, System.Int64, ...,System.Double,... всегда можно сказать, какой из сравниваемых элементов больше, а какой меньше.
- Сортировка элементов массива (массива целевых элементов) также может быть произведена с помощью вспомогательного массива ключей:
public static void Sort(Array, Array);