Класс, структура, интерфейс, перечисление, делегат – это все разнообразные категории классов. Каждая категория имеет свои особенности объявления, свое назначение и строго определенную область применения.
Делегат – это класс.
Объявление класса делегата начинается ключевым словом delegate и выглядит следующим образом:
ОбъявлениеКлассаДелегата ::=
[СпецификаторДоступа]
delegate
СпецификаторВозвращаемогоЗначения
ИмяКлассаДелегата
(СписокПараметров);
При этом
СпецификаторВозвращаемогоЗначения ::= ИмяТипа ИмяКлассаДелегата ::= Идентификатор
а синтаксис элемента СписокПараметров аналогичен списку параметров функции. Но сначала – примеры объявления классов делегатов:
delegate int ClassDelegate(int key); delegate void XXX(int intKey, float fKey);
Подобие объявления класса-делегата и заголовка функции не случайно.
Класс-делегат способен порождать объекты. При этом назначение объекта — представителя класса-делегата заключается в представлении методов (функций-членов) РАЗЛИЧНЫХ классов. В частности, тех которые оказываются видимыми из пространства имен, содержащих
Любой класс-делегат наследует 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
{
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();
// Статическая функция – член класса – в списке делегата.
// Поскольку делегат настраивается непосредственно в
// классе, которому принадлежит данная статическая функция,
// здесь обходимся без дополнительной спецификации имени
// функции.
asd.d = new xDelegate(StartClassF0);
// Вот показали имя метода, на который настроен делегат.
Console.WriteLine(asd.d.Method.ToString());
// Попытались показать имя класса – хозяина метода, на который
// настроили делегат. Ничего не получится, поскольку метод –
// статический.
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'а.
xDelegate[] dlgArray;
try
{
dlgArray = asd.d.GetInvocationList();
}
catch (System.NullReferenceException e)
{
Console.WriteLine(e.Message + ":Delegate is empty");
return;
}
// Прочитали содержимое массива описателей типа ссылок.
for (i = 0, n = dlgArray.Length; i < n; i++)
{
Console.WriteLine(dlgArray[i].Method.ToString());
// А можно и так, без помощи метода ToString()... С тем же результатом.
//Console.WriteLine(dlgArray[i].Method);
}
// Вот как запускается ОДНА функция из списка ссылок на функции!
// Используются разные варианты запуска.
Console.WriteLine("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
dlgArray[1].DynamicInvoke(new object[]{75});
((xDelegate)(dlgArray[2]))(123);
Console.WriteLine("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
// Используем этот массив для преобразования массива ссылок делегата.
// Выкидываем из массива один элемент.
// Возвращаемое значение (НОВЫЙ делегат с измененным списком)
// может быть "поймано" и другим делегатом!
asd.d = (xDelegate)xDelegate.Remove(asd.d,dlgArray[1]);
// Таблица ссылок модифицированного делегата сократилась!
Console.WriteLine("Таблица ссылок сократилась! ");
asd.d(125);
// Через свойство получили ссылку на делегат.
xDelegate dFROMasd = asd.D;
// Таблица ссылок опустошается!
// Еще бы! Здесь из списка методов одного делегата
// удаляются ВСЕ делегаты, которые встречаются в списке
// методов второго делегата. Буквально: удали из этого списка
// ВСЕ методы, которые встретишь в этом списке!
// Что-то из жизни того парня, который вытягивал сам себя
// за волосы из болота! И если бы НОВЫЙ делегат не был бы
// перехвачен на другую ссылку – весь исходный список
// методов Multicast'ового делегата asd.d был бы потерян.
dFROMasd = (xDelegate)xDelegate.RemoveAll(asd.d,asd.d);
// В чем и можно убедиться вот таким способом!
try
{
dlgArray = dFROMasd.GetInvocationList();
}
catch (System.NullReferenceException e)
{
Console.WriteLine(e.Message + ":Delegate is empty");
}
// Но только не исходный делегат!
Console.WriteLine("Но только не исходный делегат!");
// В чем и можно убедиться вот таким способом!
try
{
dlgArray = asd.d.GetInvocationList();
}
catch (System.NullReferenceException e)
{
Console.WriteLine(e.Message + ":Delegate is empty");
}
// Вот! Исходный объект класса-делегата – не пустой!
asd.d(125);
q.QQQ(75,asd.d);
}//==============================================================
}
}
Если в классе объявить член-событие, то объект — представитель этого класса сможет уведомлять объекты других классов о данном событии.
Класс, содержащий объявление события, поддерживает:
Реализация механизма управления по событиям предполагает два этапа:
Два примера иллюстрируют технику применения событий и функциональные возможности объекта события.
Первый пример:
using System;
namespace Events01
{
// Делегат.
delegate int ClassDLG (int key);
// Классы, содержащие методы – обработчики событий.
class C1
{//==============================================================
public int C1f (int key)
{//______________________________________________________________
Console.WriteLine("C1.C0f");
return key*2;
}//______________________________________________________________
}//==============================================================
class C2
{//==============================================================
public int C2f (int key)
{//______________________________________________________________
Console.WriteLine("C2.C0f");
return key*2;
}//______________________________________________________________
}//==============================================================
// Стартовый класс.
class C0
{//==============================================================
static event ClassDLG ev0;
static event ClassDLG ev1;
public static int C0F (int key)
{//______________________________________________________________
Console.WriteLine("C0.C0F");
return key*2;
}//______________________________________________________________
public int C0f (int key)
{//______________________________________________________________
Console.WriteLine("C0.C0f");
return key*2;
}//______________________________________________________________
static void Main(string[] args)
{//______________________________________________________________
int i;
Random rnd = new Random();
// Создается объект – представитель класса C1.
C1 obj1 = new C1();
// Подписная кампания. Подписываются ВСЕ!
// В событии ev0 заинтересован:
// класс C0 статическим методом C0F.
ev0 += new ClassDLG(C0.C0F);
// В событии ev1 заинтересован:
// класс C0 методом C0f, вызываемым от имени безымянного объекта,
// класс C1 методом C1f, вызываемым от имени объекта obj1,
// класс C2 методом C2f, вызываемым от имени безымянного объекта.
ev1 += new ClassDLG((new C0()).C0f);
ev1 += new ClassDLG(obj1.C1f);
ev1 += new ClassDLG((new C2()).C2f);
// И вот какие события...
// Орел или Решка!
for (i = 0; i < 100; i++)
{
Console.WriteLine("==={0}===",i);
switch (rnd.Next(0,2))
{
case 0: // Уведомляются ВСЕ заинтересованные в событии "выпадение 1".
ev0(i);
break;
case 1: // Уведомляются ВСЕ заинтересованные в событии "выпадение 0".
ev1(i);
break;
}
}
}//______________________________________________________________
}//==============================================================
}
Второй пример:
using System;
namespace EVENTS
{
// Объявленние классов-делегатов.
// Класс-делегат со стандартными сигнатурой и спецификатором
// возвращаемого значения.
delegate int ClassDlg_1(int key, string str);
// Сигнатура делегата и возвращаемое значение могут быть любыми!
delegate int ClassDlg_2(int key);
// Тип второго параметра делегата для этого класса-делегата объявлен
// ниже.
delegate void ClassDlg_TimeInfo
(object sourseKey, TimeInfoEventArgs eventKey);
//===============================================================
class TimeInfoEventArgs: EventArgs
{//==============================================================
public TimeInfoEventArgs(int hh, int mm, int ss)
{
hour = hh;
minute = mm;
second = ss;
}
public readonly int hour;
public readonly int minute;
public readonly int second;
}//==============================================================
//===============================================================
// Объявляются два класса
//===============================================================
class Doll_1
{
public ClassDlg_1 dlg1Doll_1; // Объявлен делегат "стандартного" вида.
public ClassDlg_2 dlg2Doll_1; // Объявлен делегат.
public ClassDlg_TimeInfo dlgTimeInfo; // Нужна ссылка на объект - параметр делегата.
// Объявлено соответствующее событие.
// Событие объявляется на основе класса делегата.
// Нет класса делегата – нет и события!
public event ClassDlg_2 eventDoll_1;
// Конструктор. В конструкторе производится настройка делегатов.
public Doll_1()
{
dlg1Doll_1 = new ClassDlg_1(F2Doll_1);
dlgTimeInfo = new ClassDlg_TimeInfo(TimeInfoDoll_1);
}
// Функции - члены класса, которые должны вызываться
// в ответ на уведомление о событии, на которое предположительно
// должен подписаться объект - представитель данного класса.
//===============================================================
public int F0Doll_1(int key)
{
// Эта функция только сообщает...
Console.WriteLine("this is F0Doll_1({0})", key);
return 0;
}
public void F1Doll_1(int key)
{
// Эта функция еще и УВЕДОМЛЯЕТ подписавшийся у нее объект...
// Что это за объект, функция не знает!
Console.WriteLine("this is F1Doll_1({0}), fire eventDoll_1!", key);
if (eventDoll_1 != null) eventDoll_1(key);
}
public int F2Doll_1(int key, string str)
{
// Эта функция сообщает и возвращает значение...
Console.WriteLine("this is F2Doll_1({0},{1})", key, str);
return key;
}
void TimeInfoDoll_1(object sourseKey,TimeInfoEventArgs eventKey)
{
// А эта функция вникает в самую суть события, которое несет
// информацию о времени возбуждения события.
Console.Write("event from {0}:", ((SC)sourseKey).ToString());
Console.WriteLine("the time is {0}:{1}:{2}",eventKey.hour.ToString(),
eventKey.minute.ToString(),
eventKey.second.ToString());
}
}
//===============================================================
//===============================================================
// Устройство второго класса проще. Нет обработчика события "времени".
class Doll_2
{
public ClassDlg_1 dlg1Doll_2;
public ClassDlg_2 dlg2Doll_2;
public event ClassDlg_2 eventDoll_2;
// В конструкторе производится настройка делегатов.
public Doll_2()
{
dlg1Doll_2 = new ClassDlg_1(F1Doll_2);
dlg2Doll_2 = new ClassDlg_2(F0Doll_2);
}
public int F0Doll_2(int key)
{
// Эта функция только сообщает...
Console.WriteLine("this is F0Doll_2({0})", key);
return 0;
}
public int F1Doll_2(int key, string str)
{
// Эта функция сообщает и возвращает значение...
Console.WriteLine("this is F1Doll_2({0},{1})", key, str);
return key;
}
public void F2Doll_2(int key)
{
// Эта функция еще и УВЕДОМЛЯЕТ подписавшийся у нее объект...
Console.WriteLine("this is F2Doll_2({0}), fire eventDoll_2!", key);
if (eventDoll_2 != null) eventDoll_2(key);
}
}
//===============================================================
//===============================================================
class SC // Start Class
{
// Объявлен класс-делегат...
public static ClassDlg_2 dlgSC;
// В стартовом класса объявлены два события.
public static event ClassDlg_1 eventSC;
public static event ClassDlg_TimeInfo timeInfoEvent;
static public int FunSC(int key)
{
Console.WriteLine("this is FunSC({0}) from SC",key);
// Первое уведомление. В теле этой функции осуществляется передача
// управления методу ОБЪЕКТА, который заинтересован в данном событии.
// В данном случае событием в буквальном смысле является факт передачи
// управления функции FunSC. Какие объекты заинтересованы в этом
// событии – сейчас сказать невозможно. Здесь только уведомляют о
// событии. Подписывают заинтересованных – в другом месте!
eventSC(2*key, "Hello from SC.FunSC !!!");
DateTime dt = System.DateTime.Now;
// Второе уведомление.
timeInfoEvent(new SC(), new TimeInfoEventArgs(dt.Hour,dt.Minute,dt.Second));
return key;
}
static void Main(string[] args)
{
// Объявили и определили два объекта.
// Так вот кто интересуется событиями!
Doll_1 objDoll_1 = new Doll_1();
Doll_2 objDoll_2 = new Doll_2();
// Подписали их на события.
// В событии eventSC заинтересованы объекты
// objDoll_1 и objDoll_2, которые здесь подписываются на
// события при помощи делегатов, которые были настроены
// на соответствующие функции в момент создания объекта.
// Конструкторы обоих классов только и сделали, что настроили
// собственные делегаты.
eventSC += objDoll_1.dlg1Doll_1;
eventSC += objDoll_2.dlg1Doll_2;
// А в событии timeInfoEvent заинтересован лишь
// объект - представитель класса Doll_1.
timeInfoEvent += objDoll_1.dlgTimeInfo;
// Определили делегата для функции SC.FunSC.
// Это собственная статическая функция класса SC.
dlgSC = new ClassDlg_2(SC.FunSC);
// А теперь достраиваем делегаты, которые
// не были созданы и настроены в конструкторах
// соответствующих классов.
objDoll_1.dlg2Doll_1 = new ClassDlg_2(objDoll_2.F0Doll_2);
objDoll_2.dlg1Doll_2 = new ClassDlg_1(objDoll_1.F2Doll_1);
// Подписали объекты на события.
objDoll_1.eventDoll_1 +=new ClassDlg_2(objDoll_2.F0Doll_2);
objDoll_2.eventDoll_2 +=new ClassDlg_2(objDoll_1.F0Doll_1);
// SC будет сообщать о времени!
// И неважно, откуда последует вызов функции.
// Событие в классе objDoll_1 используется для вызова
// статического метода класса SC.
objDoll_1.eventDoll_1 +=new ClassDlg_2(SC.dlgSC);
// Событие в классе objDoll_2 используется для вызова
// статического метода класса SC.
objDoll_2.eventDoll_2 +=new ClassDlg_2(SC.dlgSC);
// Работа с делегатами.
objDoll_1.dlg2Doll_1(125);
objDoll_2.dlg1Doll_2(521, "Start from objDoll_2.dlg1Doll_2");
// Работа с событиями. Уведомляем заинтересованные классы и объекты!
// Что-то не видно особой разницы с вызовами через делегатов.
objDoll_1.F1Doll_1(125);
objDoll_2.F2Doll_2(521);
// Отключили часть приемников.
// То есть отказались от получения некоторых событий.
// И действительно. Нечего заботиться о чужом классе.
objDoll_1.eventDoll_1 –= new ClassDlg_2(SC.dlgSC);
objDoll_2.eventDoll_2 –= new ClassDlg_2(SC.dlgSC);
// Опять работа с событиями.
objDoll_1.F1Doll_1(15);
objDoll_2.F2Doll_2(51);
}
}
}
Прежде всего, дадим определение АНОНИМНОГО МЕТОДА. В C# 2.0 это фрагмент кода, который оформляется в виде блока и располагается в теле метода или конструктора. Анонимный метод имеет характерный "заголовок", который содержит ключевое слово delegate и список параметров анонимного метода. Внутри блока, представляющего анонимный метод, действуют общепринятые правила областей видимости (scope). Анонимный метод выступает в контексте выражения инициализации делегата и при выполнении содержащего анонимный метод кода НЕ выполняется. В этот момент происходит инициализация делегата, которому все равно, на ЧТО он настроен: непосредственно на метод или на блок операторов в методе (то есть на анонимный метод).
Далее приводится пример объявления анонимного метода, настройки и активизации делегата, в результате которой и происходит выполнение кода анонимного метода:
using System;
using System.Collections.Generic;
using System.Text;
namespace DelegatesForAnonymousMethods
{
delegate int DDD(char op, int key1, int key2);
class DAM
{
public static int DAMstatic(char chKey, int iKey1, int iKey2)
{
// Это простой блок операторов.
// При желании его можно использовать как АНОНИМНЫЙ МЕТОД.
// Для этого всего лишь надо будет дописать выражение инициализации
// делегата. Что и будет сделано в свое время.
{
int ret;
ret = iKey1 % iKey2;
}
//return ret; // Внимание. Здесь ret не определен!
Console.WriteLine("DAMstatic: {0},{1},{2}", chKey, iKey1, iKey2);
return 0;
}
// Делегат как параметр!
// Функция модифицирует значение делегата, передаваемого как ссылка
// на ссылку, добавляя код еще одного анонимного метода,
// и выполняет при этом свою собственную работу.
public int xDAM(ref DDD dKey)
{
Console.WriteLine("Hello from xDAM!");
dKey += delegate(char op, int key1, int key2)
{
Console.WriteLine("this code was from xDAM");
return
((string)(op.ToString()+key1.ToString()+key2.ToString())).GetHashCode();
};
int x = 0;
while (true)
{
x++;
if (x == 100) break;
}
return 0;
}
// И опять делегат как параметр...
// Выполнение набранного кода.
public void goDAM(DDD dKey)
{
int ret;
Console.WriteLine("this is goDAM!");
ret = dKey('+', 5, 5);
Console.WriteLine(ret);
}
}
class Program
{
static void Main(string[] args)
{
DDD d0;
int x;
x = 0;
// Это АНОНИМНЫЙ МЕТОД.
// Объявление анонимного метода - составная часть выражения инициализации
// делегата. В момент инициализации сам код не выполняется!
d0 = delegate(char op, int key1, int key2)
{
int res;
switch (op)
{
case '+':
res = key1 + key2;
break;
case '-':
res = key1 - key2;
break;
case '*':
res = key1 * key2;
break;
case ':':
try
{
res = key1 / key2;
}
catch
{
Console.Write("That was a wrong number! ");
res = int.MaxValue;
}
break;
default:
Console.Write("That was illegal symbol of operation! ");
res = int.MinValue;
break;
}
Console.WriteLine(">>>{0}<<<", res);
return res;
};// Конец кода для инициализации делегата (конец анонимного метода).
x++;
// Делегат настраивается на фрагмент кода.
// И вот теперь этот код выполняется...
x = d0('*', 125, 7);
Console.WriteLine(x);
// Даже будучи настроенным на анонимный метод, делегат остается делегатом!
// Вот он настраивается на полноценную статическую функцию...
d0 += new DDD(DAM.DAMstatic);
x = d0(':', 125, 25);
Console.WriteLine(x);
Console.WriteLine("___________________________________________");
// А вот сам выступает в качестве параметра
// и передается функции, в которой делегату
// передается ссылка еще на один анонимный метод.
DAM dam = new DAM();
dam.xDAM(ref d0);
x = d0('-', 125, 5);
Console.WriteLine(x);
Console.WriteLine("___________________________________________");
dam.goDAM(d0);
Console.WriteLine("____________________________________________");
}
}
}
Так в чем же разница между событиями и делегатами в .NET?
В последнем примере предыдущего раздела при объявлении события очевидно его строгое соответствие определенному делегату:
public static event System.EventHandler xEvent;
System.EventHandler – это ТИП ДЕЛЕГАТА! Оператор, который обеспечивает процедуру "подписания на уведомление", полностью соответствует оператору модификации многоадресного делегата. Аналогичным образом дело обстоит и с процедурой "отказа от уведомления":
BaseClass.xEvent += new System.EventHandler(this.MyFun); BaseClass.xEvent –= new System.EventHandler(xxx.MyFun);
И это действительно так. За операторными функциями += и –= скрываются методы классов-делегатов (в том числе и класса-делегата System.EventHandler ).
Более того. Если в последнем примере в объявлении события ВЫКИНУТЬ ключевое слово event –
public static event System.EventHandler xEvent;
и заменить его на
public static System.EventHandler xEvent;
System.EventHandler (это класс-делегат!), то ничего не произойдет. Вернее, ВСЕ будет происходить, как и раньше! Вместо пары СОБЫТИЕ-ДЕЛЕГАТ будет работать пара ДЕЛЕГАТ-ДЕЛЕГАТ.
Таким образом, функционально событие является всего лишь разновидностью класса-делегата, главной задачей которого является обеспечение строгой привязки делегата к соответствующему событию.
Модификатор event вносит лишь незначительные синтаксические нюансы в использование этого МОДИФИЦИРОВАННОГО делегата — чтобы хоть как-нибудь различать возбудителя и получателя события.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.