Практическое руководство. Определение и использование делегатов (C++/CLI)

В этой статье показано, как определить и использовать делегаты в C++/CLI.

Хотя платформа .NET Framework предоставляет ряд делегатов, иногда может потребоваться определить новые делегаты.

В следующем примере кода определяется делегат, который называется MyCallback. Код обработки событий — функция, вызываемая при запуске этого нового делегата, должна иметь тип возвращаемого void значения и получить ссылку String .

Основная функция использует статический метод, определенный SomeClass для создания экземпляра делегата MyCallback . Затем делегат становится альтернативным методом вызова этой функции, как показано путем отправки строки "single" в объект делегата. Затем дополнительные экземпляры MyCallback связаны друг с другом, а затем выполняются одним вызовом объекта делегата.

// use_delegate.cpp
// compile with: /clr
using namespace System;

ref class SomeClass
{
public:
   static void Func(String^ str)
   {
      Console::WriteLine("static SomeClass::Func - {0}", str);
   }
};

ref class OtherClass
{
public:
   OtherClass( Int32 n )
   {
      num = n;
   }

   void Method(String^ str)
   {
      Console::WriteLine("OtherClass::Method - {0}, num = {1}",
         str, num);
   }

   Int32 num;
};

delegate void MyCallback(String^ str);

int main( )
{
   MyCallback^ callback = gcnew MyCallback(SomeClass::Func);
   callback("single");

   callback += gcnew MyCallback(SomeClass::Func);

   OtherClass^ f = gcnew OtherClass(99);
   callback += gcnew MyCallback(f, &OtherClass::Method);

   f = gcnew OtherClass(100);
   callback += gcnew MyCallback(f, &OtherClass::Method);

   callback("chained");

   return 0;
}
static SomeClass::Func - single
static SomeClass::Func - chained
static SomeClass::Func - chained
OtherClass::Method - chained, num = 99
OtherClass::Method - chained, num = 100

В следующем примере кода показано, как связать делегата с членом класса значений.

// mcppv2_del_mem_value_class.cpp
// compile with: /clr
using namespace System;
public delegate void MyDel();

value class A {
public:
   void func1() {
      Console::WriteLine("test");
   }
};

int main() {
   A a;
   A^ ah = a;
   MyDel^ f = gcnew MyDel(a, &A::func1);   // implicit box of a
   f();
   MyDel^ f2 = gcnew MyDel(ah, &A::func1);
   f2();
}
test
test

Создание делегатов

Оператор "-" можно использовать для удаления делегата компонента из составного делегата.

// mcppv2_compose_delegates.cpp
// compile with: /clr
using namespace System;

delegate void MyDelegate(String ^ s);

ref class MyClass {
public:
   static void Hello(String ^ s) {
      Console::WriteLine("Hello, {0}!", s);
   }

   static void Goodbye(String ^ s) {
      Console::WriteLine("  Goodbye, {0}!", s);
   }
};

int main() {

   MyDelegate ^ a = gcnew MyDelegate(MyClass::Hello);
   MyDelegate ^ b = gcnew MyDelegate(MyClass::Goodbye);
   MyDelegate ^ c = a + b;
   MyDelegate ^ d = c - a;

   Console::WriteLine("Invoking delegate a:");
   a("A");
   Console::WriteLine("Invoking delegate b:");
   b("B");
   Console::WriteLine("Invoking delegate c:");
   c("C");
   Console::WriteLine("Invoking delegate d:");
   d("D");
}

Выходные данные

Invoking delegate a:
Hello, A!
Invoking delegate b:
  Goodbye, B!
Invoking delegate c:
Hello, C!
  Goodbye, C!
Invoking delegate d:
  Goodbye, D!

Передайте делегат^ в собственную функцию, которая ожидает указатель функции

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

В этом примере создается библиотека DLL, экспортируемая собственная функция:

// delegate_to_native_function.cpp
// compile with: /LD
#include < windows.h >
extern "C" {
   __declspec(dllexport)
   void nativeFunction(void (CALLBACK *mgdFunc)(const char* str)) {
      mgdFunc("Call to Managed Function");
   }
}

Следующий пример использует библиотеку DLL и передает дескриптор делегата в собственную функцию, которая ожидает указатель функции.

// delegate_to_native_function_2.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;

delegate void Del(String ^s);
public ref class A {
public:
   void delMember(String ^s) {
      Console::WriteLine(s);
   }
};

[DllImportAttribute("delegate_to_native_function", CharSet=CharSet::Ansi)]
extern "C" void nativeFunction(Del ^d);

int main() {
   A ^a = gcnew A;
   Del ^d = gcnew Del(a, &A::delMember);
   nativeFunction(d);   // Call to native function
}

Выходные данные

Call to Managed Function

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

Чтобы связать делегат с собственной функцией, необходимо упаковать собственную функцию в управляемый тип и объявить функцию, вызываемую через PInvoke.

// mcppv2_del_to_umnangd_func.cpp
// compile with: /clr
#pragma unmanaged
extern "C" void printf(const char*, ...);
class A {
public:
   static void func(char* s) {
      printf(s);
   }
};

#pragma managed
public delegate void func(char*);

ref class B {
   A* ap;

public:
   B(A* ap):ap(ap) {}
   void func(char* s) {
      ap->func(s);
   }
};

int main() {
   A* a = new A;
   B^ b = gcnew B(a);
   func^ f = gcnew func(b, &B::func);
   f("hello");
   delete a;
}

Выходные данные

hello

Использование необвязанных делегатов

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

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

Вот как объявить, создать экземпляр и вызвать привязанные и несвязанные делегаты:

Действие Привязанные делегаты Несвязанные делегаты
Declare Подпись делегата должна соответствовать сигнатуре функции, которую необходимо вызвать через делегат. Первым параметром сигнатуры делегата this является тип объекта, который требуется вызвать.

После первого параметра подпись делегата должна соответствовать сигнатуре функции, которую вы хотите вызвать через делегат.
Создания При создании экземпляра привязанного делегата можно указать функцию экземпляра или глобальную или статическую функцию-член.

Чтобы указать функцию экземпляра, первый параметр — это экземпляр типа, функция-член которого требуется вызвать, а второй параметр — адрес функции, которую требуется вызвать.

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

В этом примере показано, как объявлять, создавать экземпляры и вызывать несвязанные делегаты:

// unbound_delegates.cpp
// compile with: /clr
ref struct A {
   A(){}
   A(int i) : m_i(i) {}
   void Print(int i) { System::Console::WriteLine(m_i + i);}

private:
   int m_i;
};

value struct V {
   void Print() { System::Console::WriteLine(m_i);}
   int m_i;
};

delegate void Delegate1(A^, int i);
delegate void Delegate2(A%, int i);

delegate void Delegate3(interior_ptr<V>);
delegate void Delegate4(V%);

delegate void Delegate5(int i);
delegate void Delegate6();

int main() {
   A^ a1 = gcnew A(1);
   A% a2 = *gcnew A(2);

   Delegate1 ^ Unbound_Delegate1 = gcnew Delegate1(&A::Print);
   // delegate takes a handle
   Unbound_Delegate1(a1, 1);
   Unbound_Delegate1(%a2, 1);

   Delegate2 ^ Unbound_Delegate2 = gcnew Delegate2(&A::Print);
   // delegate takes a tracking reference (must deference the handle)
   Unbound_Delegate2(*a1, 1);
   Unbound_Delegate2(a2, 1);

   // instantiate a bound delegate to an instance member function
   Delegate5 ^ Bound_Del = gcnew Delegate5(a1, &A::Print);
   Bound_Del(1);

   // instantiate value types
   V v1 = {7};
   V v2 = {8};

   Delegate3 ^ Unbound_Delegate3 = gcnew Delegate3(&V::Print);
   Unbound_Delegate3(&v1);
   Unbound_Delegate3(&v2);

   Delegate4 ^ Unbound_Delegate4 = gcnew Delegate4(&V::Print);
   Unbound_Delegate4(v1);
   Unbound_Delegate4(v2);

   Delegate6 ^ Bound_Delegate3 = gcnew Delegate6(v1, &V::Print);
   Bound_Delegate3();
}

Выходные данные

2
3
2
3
2
7
8
7
8
7

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

// unbound_delegates_2.cpp
// compile with: /clr
using namespace System;

ref class RefClass {
   String^ _Str;

public:
   RefClass( String^ str ) : _Str( str ) {}
   void Print() { Console::Write( _Str ); }
};

delegate void PrintDelegate( RefClass^ );

int main() {
   PrintDelegate^ d = gcnew PrintDelegate( &RefClass::Print );

   array< RefClass^ >^ a = gcnew array<RefClass^>( 10 );

   for ( int i = 0; i < a->Length; ++i )
      a[i] = gcnew RefClass( i.ToString() );

   for each ( RefClass^ R in a )
      d( R );

   Console::WriteLine();
}

В этом примере создается несвязанный делегат для функций доступа свойства:

// unbound_delegates_3.cpp
// compile with: /clr
ref struct B {
   property int P1 {
      int get() { return m_i; }
      void set(int i) { m_i = i; }
   }

private:
   int m_i;
};

delegate void DelBSet(B^, int);
delegate int DelBGet(B^);

int main() {
   B^ b = gcnew B;

   DelBSet^ delBSet = gcnew DelBSet(&B::P1::set);
   delBSet(b, 11);

   DelBGet^ delBGet = gcnew DelBGet(&B::P1::get);
   System::Console::WriteLine(delBGet(b));
}

Выходные данные

11

В следующем примере показано, как вызвать делегат многоадресной рассылки, где один экземпляр привязан и один экземпляр не подключен.

// unbound_delegates_4.cpp
// compile with: /clr
ref class R {
public:
   R(int i) : m_i(i) {}

   void f(R ^ r) {
      System::Console::WriteLine("in f(R ^ r)");
   }

   void f() {
      System::Console::WriteLine("in f()");
   }

private:
   int m_i;
};

delegate void Del(R ^);

int main() {
   R ^r1 = gcnew R(11);
   R ^r2 = gcnew R(12);

   Del^ d = gcnew Del(r1, &R::f);
   d += gcnew Del(&R::f);
   d(r2);
};

Выходные данные

in f(R ^ r)
in f()

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

// unbound_delegates_5.cpp
// compile with: /clr
ref struct R {
   R(int i) : m_i(i) {}

   int f(R ^) { return 999; }
   int f() { return m_i + 5; }

   int m_i;
};

value struct V {
   int f(V%) { return 999; }
   int f() { return m_i + 5; }

   int m_i;
};

generic <typename T>
delegate int Del(T t);

generic <typename T>
delegate int DelV(T% t);

int main() {
   R^ hr = gcnew R(7);
   System::Console::WriteLine((gcnew Del<R^>(&R::f))(hr));

   V v;
   v.m_i = 9;
   System::Console::WriteLine((gcnew DelV<V >(&V::f))(v) );
}

Выходные данные

12
14

См. также

delegate (расширения компонентов C++)