
Объектно-ориентированное программирование.-7
.pdf
лезно в том случае, если в классе или структуре реализуется внутренний интерфейс, который не предоставляет интереса для объекта, где используется этот класс или эта структура. Пример:
public interface ITest2
{
int Count();
}
class MyClass3 : ITest2
{
public int ITest2.Count() { return 3; } // Ошибка int ITest2.Count() { return 3; } // ОК
}
class MyClass4 : ITest2
{
public int Count() { return 4; } // ОК
}
static int Main()
{
MyClass3 c3 = new MyClass3(); MyClass4 c4 = new MyClass4();
Console.WriteLine(c3.Count()); // Ошибка Console.WriteLine(c4.Count()); // 4 return 0;
}
Во-вторых, для избежания неоднозначности имен. Явные реализации члена интерфейса позволяют разрешить неоднозначности членов интерфейса с одинаковой сигнатурой. Без этого было бы невозможно иметь разные реализации члена интерфейса с одинаковыми сигнатурой и типом возвращаемого значения, а также было бы невозможно включить в класс или структуру любые реализации членов интерфейса с одинаковой сигнатурой и разными типами возвращаемых значений. Пример:
class MyClass5 : ITest, ITest2
{
public void F(out string s)
{
s = "MyClass5";
}
int ITest.Count
{
get { return 5; }
}
public string this[int i]
{
get { return "MyClass5.ITest." + i; } set { }
331

}
int ITest2.Count()
{
return 5;
}
public event TestD TestEvent;
}
В данном примере без явной квалификации наблюдался бы конфликт имен для членов Count.
Доступ к явной реализации члена интерфейса можно получить, используя преобразование ссылки на экземпляр класса к типу интерфейса:
static int Main()
{
MyClass3 c3 = new MyClass3();
Console.WriteLine(((ITest2)c3).Count()); // 3 return 0;
}
4.9.2.2. Запрос о реализации интерфейса
Что будет, если клиент попытается использовать класс так, как если бы в нем был реализован метод, на самом деле в нем не реализованный? Проверим:
MyClass2 c2 = new MyClass2();
Console.WriteLine(((ITest2)c2).Count());
Получаем исключительную ситуацию System.InvalidCastException. Поэтому при получении ссылки на объект и не будучи уверенными в том, что для данного объекта реализуется тот или иной интерфейс, это нужно предварительно проверить с помощью оператора is или as:
static void F1(object x)
{
if (x is ITest2) Console.WriteLine(((ITest2)x).Count()); else Console.WriteLine("Интерфейс ITest2 не реализован");
}
static void F2(object x)
{
ITest2 intf = x as ITest2;
if (intf != null) Console.WriteLine(intf.Count());
else Console.WriteLine("Интерфейс ITest2 не реализован");
}
static int Main()
{
332

MyClass2 c2 = new MyClass2();
MyClass4 c4 = new MyClass4();
F1(c2); // Интерфейс ITest2 не реализован F2(c2); // Интерфейс ITest2 не реализован
F2(c4); // 4 return 0;
}
4.9.2.3. Доступ к членам интерфейса
Любой класс, реализующий интерфейсы, имеет неявное преобразование типа к типам этих интерфейсов. Соответственно, доступ к реализованным членам интерфейса осуществляется так же, как доступ к членам класса с такой же сигнатурой. При этом доступ возможен и по ссылке на экземпляр класса, и по ссылке на реализацию интерфейса.
Пример:
static void F3(ITest i)
{
string s; i.F(out s);
Console.WriteLine(s);
Console.WriteLine(i.Count);
Console.WriteLine(i[5]);
}
static int Main()
{
MyClass2 c2 = new MyClass2();
F3(c2); return 0;
}
Вывод на консоль:
MyClass2
2
MyClass2.ITest.5
Получить ссылку на интерфейс экземпляра класса MyClass5 не получится, т.к. он реализует сразу два интерфейса. Однако, можно эти интерфейсы комбинировать:
interface ITestComb : ITest, ITest2
{
}
class MyClass5 : ITestComb
// и т.д.
А чтобы не возникала неоднозначность при доступе к члену Count, ис-
333

пользуем преобразование типа ссылки на реализацию интерфейса:
static void F4(ITestComb i)
{
string s; i.F(out s);
Console.WriteLine(s); Console.WriteLine(((ITest)i).Count); Console.WriteLine(((ITest2)i).Count()); Console.WriteLine(i[7]);
}
static int Main()
{
MyClass5 c5 = new MyClass5();
F4(c5); return 0;
}
Вывод на консоль:
MyClass5
5
5
MyClass5.ITest.7
4.9.3. Интерфейсы и наследование
Рассмотрим особенности реализации интерфейсов при наследовании классов, их реализующих.
Пример: Samples\4.9\4_9_3_inherit.
4.9.3.1. Наследование реализаций интерфейсов
Класс наследует все реализации интерфейсов, содержащиеся в его базовых классах. Без явной повторной реализации интерфейса, в производном классе нельзя изменить сопоставление интерфейсов, унаследованных им из базовых классов. Тем не менее, при сопоставлении метода интерфейса с виртуальным методом в классе можно переопределить этот виртуальный метод в производных классах и изменить реализацию интерфейса. Пример:
interface ITest
{
void Method1(); void Method2();
}
class Base : ITest
{
public void Method1()
334

{
Console.WriteLine("Base.Method1");
}
public virtual void Method2()
{
Console.WriteLine("Base.Method2");
}
}
class Child : Base
{
public new void Method1()
{
Console.WriteLine("Child.Method1");
}
public override void Method2()
{
Console.WriteLine("Child.Method2");
}
}
static int Main()
{
Base b = new Base(); Child c = new Child();
b.Method1(); // Base.Method1 c.Method1(); // Child.Method1 ((ITest)b).Method1(); // Base.Method1 ((ITest)c).Method1(); // Base.Method1
b.Method2(); // Base.Method2 c.Method2(); // Child.Method2 ((ITest)b).Method2(); // Base.Method2 ((ITest)c).Method2(); // Child.Method2 return 0;
}
Так как явные реализации метода интерфейса не могут быть объявлены виртуальными, переопределить явную реализацию члена интерфейса невозможно. Однако в явной реализации члена интерфейса можно вызвать другой метод, который может быть объявлен виртуальным, что позволяет переопределить его в производных классах.
4.9.3.2. Повторная реализация интерфейсов
В классе, который наследует реализацию интерфейса, разрешается повторно реализовать этот интерфейс путем включения его в список базовых классов. В процедуре сопоставления повторно реализованных интерфейсов участвуют унаследованные объявления открытых членов и унаследованные явные объявления членов интерфейса. Пример:
335

interface ITest2
{
string A(); string B(); string C(); string D();
}
class Base2 : ITest2
{
public string A() { return "B.A"; } public string B() { return "B.B"; } string ITest2.C() { return "B.C"; } string ITest2.D() { return "B.D"; }
}
class Child2 : Base2, ITest2
{
string ITest2.A() { return "C.A"; } public string D() { return "C.D"; }
}
static int Main()
{
Child2 c2 = new Child2();
Console.WriteLine(c2.A()); // B.A Console.WriteLine(c2.B()); // B.B Console.WriteLine(((ITest2)c2).C()); // B.C Console.WriteLine(((ITest2)c2).A()); // C.A Console.WriteLine(c2.D()); // C.D
return 0;
}
Вывести на консоль строку «B.D», имея экземпляр дочернего класса, не получится, т.к. соответствующая реализация является явной, а при преобразовании к ссылке на реализацию интерфейса будет вызываться метод D() дочернего класса.
4.9.3.3. Абстрактные классы и интерфейсы
В абстрактных классах, как и в классах, не являющихся абстрактными, необходимо указать реализацию всех членов интерфейсов, включенных в список их базовых классов. Тем не менее, в абстрактном классе разрешается сопоставлять методы интерфейса абстрактным методам. Пример:
public delegate void TestD();
interface ITest3
{
event TestD TestEvent;
}
abstract class AbsClass : ITest2
336

{
public abstract string A(); public string B() { return ""; } public abstract string C(); string ITest2.D() { return ""; }
public abstract event TestD TestEvent;
}
4.9.4. Примеры использования интерфейсов
Многие классы, с которыми мы познакомились к настоящему времени, реализуют те или иные интерфейсы. Кроме того, в некоторых ситуациях бывает полезно реализовать в создаваемом классе определенные интерфейсы – это может расширить список операций, доступных для обработки данных.
Как мы помним, структуры и классы типов данных реализуют следующие интерфейсы:
•Типы по значению (кроме структур) – IComparable, IFormattable (кроме логического и символьного типа), IConvertible, IEquatable (кроме перечислений);
•Структура DateTime – IComparable, IFormattable, IConvertible;
•Класс String – IComparable, ICloneable, IConvertible, IEnumerable;
•Класс Array – ICloneable, IList, ICollection, IEnumerable.
Еще мы говорили об интерфейсе IDisposable, использующемся в операторе выделения ресурсов using. Что это им дает?
Пример: Samples\4.9\4_9_4_samples.
4.9.4.1. Ранжирование объектов
Интерфейс IComparable описывает всего один член:
int CompareTo(object obj);
Реализация этого метода должна возвращать –1, если логика приложения определяет, что текущий экземпляр объекта предшествует экземпляру obj, +1, если текущий экземпляр следует за obj, и 0, если их ранг совпадает. Для чисел, дат, строк и т.п. можно провести соответствие реализации данного интерфейса и операций отношения (табл. 4.4).
Табл. 4.4 – Операции отношения и метод CompareTo
Операция отношения |
Результат метода CompareTo |
|
|
Равно (x == y) |
x.CompateTo(y) == 0 |
|
|
|
337 |

Не равно (x != y) |
x.CompateTo(y) != 0 |
|
|
|
|
Больше (x > y) |
x.CompateTo(y) > |
0 |
|
|
|
Больше или равно (x >= y) |
x.CompateTo(y) >= 0 |
|
|
|
|
Меньше (x < y) |
x.CompateTo(y) < |
0 |
|
|
|
Меньше или равно (x <= y) |
x.CompateTo(y) <= 0 |
|
|
|
|
Чаще всего экземпляры классов, реализующих данный интерфейс, используются при ранжировании объектов, сортировке массивов данных и поиске. Например, массив экземпляров любого класса, реализующего этот интерфейс, можно сортировать при помощи методов Array.Sort, либо осуществлять в нем двоичный поиск вызовом Array.BinarySearch.
Пример:
class Rational : IComparable
{
public int Numerator = 0; public int Denominator = 1;
public Rational (int num, int den)
{
Numerator = num; Denominator = den;
}
public int CompareTo(object obj)
{
if (obj is Rational)
{
Rational r = (Rational)obj;
return Math.Sign((double)Numerator / Denominator – (double)r.Numerator / r.Denominator);
}
throw new ArgumentException("Нельзя дробь сравнить с " +
(obj != null ? obj.GetType().ToString() : "null"));
}
public override string ToString()
{
return String.Format("{0}/{1}", Numerator, Denominator);
}
}
static int Main()
{
Rational[] r = new Rational[3];
r[0] = new Rational(11, 5); r[1] = new Rational(-1, 6); r[2] = new Rational(3, 8);
Array.Sort(r);
foreach (Rational x in r) Console.WriteLine(x);
338

return 0;
}
Данный класс позволяет сортировать рациональные дроби. Вывод на консоль:
-1/6 3/8 11/5
Если не реализовать в классе Rational интерфейс IComparable, при вы-
полнении получим исключение System.InvalidOperationException.
Более простой вариант ранжирования обеспечивает обобщенный интерфейс IEquatable<T>. Он также описывает только один член:
bool Equals(T other);
Реализация данного метода позволяет определить равенство или неравенство двух экземпляров объектов. Классам, реализующим данный интерфейс, можно осуществлять поиск, удаление и другие операции в универсаль-
ных коллекциях типа Dictionary<TKey, TValue>, List<T> и т.п., некоторые перегрузки метода Array.BinarySearch и т.д.
Пример:
class Rational : IEquatable<Rational>
{
/* описанное ранее тело класса */
public bool Equals(Rational r)
{
return (long)Numerator * r.Denominator == (long)r.Numerator * Denominator;
}
}
static int Main()
{
/* описанное ранее использование класса */
List<Rational> list = new List<Rational>();
list.AddRange(r);
Console.WriteLine("Индекс элемента 11/5: " + list.IndexOf(new
Rational(11, 5))); // 2 return 0;
}
339

4.9.4.2. Перечисления и коллекции
Перечислители
Основное назначение интерфейса IEnumerable – предоставление перечислителя, который поддерживает простой перебор элементов коллекции (итератор). Его реализации чаще всего используются в цикле foreach.
Интерфейс IEnumerable содержит объявление единственного метода:
IEnumerator GetEnumerator();
Этот метод возвращает экземпляр класса, реализующего другой интерфейс – перечислитель IEnumerator. Оба этих интерфейса объявлены в про-
странстве имен System.Collections. Члены интерфейса IEnumerator:
void Reset(); bool MoveNext(); object Current;
Первый метод устанавливает перечислитель в начальное положение. Второй осуществляет переход к следующему элементу перечисления. Если он возвращает false, это означает, что больше элементов нет. И последнее свойство возвращает текущий элемент. В этом случае оператор
foreach (<тип> v in x) <внедряемый оператор>
развертывается следующим образом:
{
IEnumerator e = x.GetEnumerator(); try
{
<тип> v; e.Reset();
while (e.MoveNext())
{
v = (<тип>)e.Current;
<внедряемый оператор>
}
}
finally
{
// Удаление ресурсов
}
}
Пример:
class VowelEnum : IEnumerator
{
private string S; private int pos;
340