Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Методичка по С#

.pdf
Скачиваний:
293
Добавлен:
13.02.2015
Размер:
3 Мб
Скачать
☆

class A

{

public virtual void F() { }

}

class B:A

{

new public virtual void F() { }

}

class B:A

{

public override void F() { }

}

class B:A

{

new public void F() { }

}

81

class A

{

public void F(){ }

}

class B:A

{

new public void F() { }

}

class B:A

{

new public virtual void F() { }

}

Введём следующие обозначения и построим схему наследования.

Спецификация и заголовок функциичлена

Уровень наследования

Обозначение

public void F(){ }

0

 

 

 

 

public virtual void F() { }

0

 

 

 

 

new public virtual void F() { }

1..N

 

 

 

 

new public void F() { }

1..N

 

 

 

 

public override void F() { }

1..N

 

 

 

 

Схема возможных вариантов объявления методов в иерархии наследования трёх уровней.

Наконец, замечательный пример,

81

82

using System; namespace Implementing

{

class A

{

public virtual void F() { Console.WriteLine(“A”);}

}

class B:A

{

public override void F() { Console.WriteLine(“B”); }

}

class C:B

{

new public virtual void F() { Console.WriteLine(“C”); }

}

class D:C

{

public override void F() { Console.WriteLine(“D”); }

}

class Starter

{

static void Main(string[] args)

{

D d = new D(); C c = d;

B b = c; A a = b;

d.F(); /* D */ c.F(); /* D */ b.F(); /* B */ a.F(); /* B */

}

}

}

в котором становится ВСЁ понятно, если ПОДРОБНО нарисовать схемы классов, структуру объекта-представителя класса D и посмотреть, КАКАЯ ссылка ЗА КАКОЕ место этот самый объект удерживает.

Интерфейсы

Фактически это те же самые абстрактные классы, НЕ СОДЕРЖАЩИЕ объявлений данных – членов и объявлений ОБЫЧНЫХ функций.

Все без исключения функции-члены интерфейса – абстрактные. Поэтому интерфейс объявляется с особым ключевым словом interface, а функции интерфейса, несмотря на свою “абстрактность” объявляются без ключевого слова abstract.

Основное отличие интерфейса от абстрактного класса заключается в том, что производный класс может наследовать одновременно несколько интерфейсов.

using System;

namespace Interface01

{

//Интерфейсы.

//Этот интерфейс характеризуется уникальными

//именами объявленных в нём методов. interface Ix

{

void IxF0(int xKey); void IxF1();

}

82

83

//Пара интерфейсов, содержащих объявления одноимённых методов

//с одной и той же сигнатурой.

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);

83

84

}

//А это явная непосредственная реализация интерфейса 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();

84

85

Console.WriteLine("==========Iw=========="); Iw iw = x1;

iw.F0(10);

iw.F1();

}

}

}

Преимущества программирования с использованием интерфейсов проявляются в том случае, когда ОДНИ И ТЕ ЖЕ ИНТЕРФЕЙСЫ наследуются РАЗНЫМИ классами. И здесь всё определяется спецификой данной конкретной реализации.

using System;

namespace Interface02

{

//Объявляются два интерфейса, каждый из которых содержит объявление

//одноименного метода с единственным параметром соответствующего типа.

interface ICompare0

{

bool Eq(ICompare0 obj);

}

interface ICompare1

{

bool Eq(ICompare1 obj);

}

//Объявляются классы, наследующие оба интерфейса.

//В каждом из классов реализуются функции интерфейсов.

//В силу того, что объявленные в интерфейсах методы одноименные,

//в классах применяется явная реализация методов интерфейсов.

//_______________________________________________________________________. 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;

}

}

85

86

//_______________________________________________________________________.

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");

86

87

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!");

}

}

}

Делегаты

Класс, структура, интерфейс, перечисление, делегат – это всё разнообразные категории классов. Каждая категория имеет свои особенности объявления, своё назначение и строго определённую область применения.

Делегат – это тоже класс.

Объявление класса делегата начинается ключевым словом delegate и выглядит следующим образом:

ОбъявлениеКлассаДелегата ::=

[СпецификаторДоступа] delegate

СпецификаторВозвращаемогоЗначения

ИмяКлассаДелегата (СписокПараметров);

При этом

СпецификаторВозвращаемогоЗначения ::= ИмяТипа ИмяКлассаДелегата ::= Идентификатор

87

88

а синтаксис элемента СписокПараметров аналогичен списку параметров функции. Но сначала примеры объявления классов делегатов.

delegate int ClassDelegate(int key); delegate void XXX(int intKey, float fKey);

Подобие объявления класса делегата и заголовка функции не случайно.

Класс делегат способен порождать объекты. При этом назначение объектапредставителя класса-делегата заключается в представлении методов (функцийчленов) РАЗЛИЧНЫХ классов.

Тех классов, которые оказываются видимыми из пространства имён, содержащих объявление данного класса-делегата. Объект этого класса способен представлять ЛЮБЫЕ (статические и нестатические) функции-члены классов, лишь бы спецификация их возвращаемого значения и список параметров соответствовали бы характеристикам данного класса-делегата.

В основе любой класс-делегат является многоадресным (multicast delegate). Это означает, что в ходе выполнения приложения делегат способен запоминать ссылки на произвольное количество функций-членов. Эта многоадресность обеспечивается внутренним списком, в который и заносятся ссылки на

разнообразные

функции-члены,

соответствующие

заданной

сигнатуре

и

спецификации возвращаемого значения.

 

 

 

Класс-делегат является производным от System.MulticastDelegate, что и позволяет объектам-представителям этого класса реализовывать достаточно сложные алгоритмы настройки на функции-члены различных классов.

Унаследованные свойства и методы классов-делегатов.

Свойства и методы

Назначение

 

 

 

 

 

 

 

 

Method

 

Свойство. Возвращает имя метода, на который указывает

 

 

делегат.

 

 

 

 

 

 

 

 

Target

 

Свойство. Возвращает имя класса, если делегат указывает на

 

 

нестатический метод класса. Возвращает значение типа null,

 

 

если делегат указывает на статический метод.

 

 

 

Combine(),

 

Функция и операторные функции. Обеспечивают реализацию

operator+(),

многоадресного делегата. Работа с операторными функциями

operator+=(),

смотрится как прибавление и вычитание ДЕЛЕГАТОВ. Арифметика

operator-(),

делегатов.

 

 

 

 

 

 

 

 

operator-=()

 

 

 

 

 

 

 

 

 

 

 

GetInvocationList()

Основываясь на внутреннем списке ссылок на функции, строится

 

 

соответствующий массив описателей типов функций. Попытка

 

 

применения

метода

к

пустому

делегату

приводит

к

 

 

возникновению исключения.

 

 

 

 

 

 

object

DynamicInvoke

В соответствии со списком ссылок обеспечивается выполнение

(object[] args)

функций, на которые был настроен делегат.

 

 

 

static Remove()

Статический

метод,

обеспечивающий

удаление

элементов

 

 

внутреннего списка ссылок на функции.

 

 

 

 

 

Пример использования возможностей делегата представлен ниже.

using System; namespace Delegates_1

{

//Класс делегат. Его объявление соответствует типу функции,

//возвращающей значение int с одним параметром типа int. delegate int xDelegate(int key);

//=========================================== class ASD

{

//Делегат как член класса. public xDelegate d;

//А вот свойство, которое возвращает ссылку на делегат. public xDelegate D

{

get

{

88

89

return d;

}

}

}

//========================================================

//Класс, содержащий функцию-член, на которую может

//быть настроен делегат-представитель класса xDelegate. //======================================================== class Q

{

public int QF(int key)

{

Console.WriteLine(“int QF({0})”, key); return key;

}

//А вот функция, в которой делегат используется как параметр! public void QQQ(int key, xDelegate par)

{

Console.WriteLine(“Делегат как параметр!”);

//И этот делегат используется по назначению – обеспечивает вызов

//НЕКОТОРОЙ функции. Какой функции? А неизвестно какой! Той,

//на которую был настроен делегат перед тем, как его ссылка была передана

//в функцию QQQ. Здесь не интересуются тем, что за функция пришла.

//Здесь запускается ЛЮБАЯ функция, на которую настраивался делегат. par(key);

}

}

//===========================================

//Стартовый класс. Также содержит пару пригодных для настройки

//делегата функций.

class StartClass

{

// Одна статическая...

public static int StartClassF0(int key)

{

Console.WriteLine(“int StartClassF0({0})”, key); return key;

}

//Вторая нестатическая...

public int StartClassF1(int key)

{

Console.WriteLine(“int StartClassF1({0})”, key); return key;

}

//Процесс пошёл!

static void Main(string[] args) {//===============================================================

//Ссылка на делегат. xDelegate localDelegate; int i, n;

//Объект – представитель класса StartClass. StartClass sc = new StartClass();

//Объект – представитель класса Q.

Q q = new Q();

//Объект – представитель класса ASD. ASD asd = new ASD();

//Статическая функция-член класса - в списке делегата.

//Поскольку делегат настраивается непосредственно в

89

90

//класса, которому принадлежит данная статическая функция,

//здесь обходимся без дополнительной спецификации имени

//функции.

asd.d = new xDelegate(StartClassF0);

//Вот показали имя метода, на который настроен делегат.

//Попытались показать имя класса – хозяина метода, на который

//настроили делегат. Ничего не получится, поскольку метод

//статический.

if (asd.d.Target != null) Console.WriteLine(asd.d.Target);

//неСтатическая функция-член класса - в списке делегата.

//добавляется к списку функций делегата посредством

//операторной функции +=.

asd.d += new xDelegate(sc.StartClassF1); Console.WriteLine(asd.d.Method.ToString());

if (asd.d.Target != null) Console.WriteLine(asd.d.Target);

// Делегат также включил в список функцию-член класса Q.

asd.d += new xDelegate(q.QF); Console.WriteLine(asd.d.Method.ToString());

if (asd.d.Target != null) Console.WriteLine(asd.d.Target);

//Делегат разряжается последовательностью

//вызовов разнообразных функций.

//Либо так. asd.d(125);

//Либо так. Параметр

//при этом пришлось упаковать в одномерный массив типа object

//длиной в 1.

asd.d.DynamicInvoke(new object[]{0});

//Также есть возможность удаления функции из списка делегата.

//Для этого воспользуемся локальным делегатом.

localDelegate = new xDelegate(q.QF); asd.d -= localDelegate;

asd.d(725);

// А теперь опять добавим функцию в список делегата. asd.d += localDelegate;

asd.d(325);

Console.WriteLine(asd.d.Method.ToString());

//А теперь - деятельность по построению массива описателей типа ссылок.

//Преобразование ОДНОГО объекта, содержащего массив ссылок на функции

//в массив объектов, содержащих по одной ссылке на функцию.

//Таким образом, для каждой ссылки на функцию делегата в рамках массива

//делегатов строится свой собственный делегат.

//Естественно что элемент массива, уже не является Multicast'овым делегатом.

//Поэтому с его помощью можно выполнить ОДНУ функцию Multicast’ового делегата.

Console.WriteLine(“—In array!------------------------- ”);

// Вот ссылка на массив делегетов. Сюда будем сгружать содержимое Multicast’а.

Delegate[] dlgArray; try

{

dlgArray = asd.d.GetInvocationList();

}

catch (System.NullReferenceException e)

{

Console.WriteLine(e.Message + “:Delegate is empty”); return;

}

90

Соседние файлы в предмете Языки программирования