Методичка по С#
.pdf
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
