CLR中的委托和事件

还记得这个声明吗:

    Returntype (*lpfun)(params).

     这是本地C++中的函数指针,即把此类型变量指针返回类型和实参一样的函数,就可以用此类型变量来执行此过程。

如下:

  

#include <iostream>
typedef int (*lpfun)(int&);

int getvalue(int& a)
{
return a;
}

void main()
{
lpfun p=getvalue;
int var=55;
printf("the value is: %d\n",p(var));

}

结果为:

 

和函数指针一样,在C++/CLR中的事件委托和本地C++中的函数指针是一样的。

一、声明委托

    我们在点击鼠标时会产生一个click事件,而这引起这个事件所发出信号的过程就涉及到委托。委托就是能封装一个或多个指针的对象,这些指针能指向具有特定开参列表和返回类型的函数。

    在CLR中委托的基类是System::Delegate,因此委托的类型总是要继承该基类的成员。委托实事上定义了两个事情:委托对象的引用类型名以及可能与该委托有关的函数形参列表和返回类型。

  以下是委托的声明:

    

public delegate void Handler(int Value);

该语句将委托的引用类型定义为Handler,这是从System::Delegare类派生的。类型为Handler的对象可以包含拥有一个int 类型实参和返回类型为void的一个名多个函数的指针,委托指向的可以是实例函数也可以是静态函数。

二、创建委托

     定义了委托之后就可以创建委托对象了。委托的构造函数有两个,可以接受一个实参也可以接受两个实参。接受单个实参的委托构造函数的实参必须具有委托声明中指定 的返回类型和形参列表的静态函数成员或全局函数.

    如下类中:

     

public ref class HandlerClass
{
public:
static void Fun1(int m)
{
Console::WriteLine(L"Function1 called with value {0}",m);
}

static void Fun2(int m)
{
Console::WriteLine(L"Function2 called with value {0}",m);
}
void Fun3(int m)
{
Console::WriteLine(L"Function3 called with value {0}",m);
}

void Fun4(int m)
{
Console::WriteLine(L"Function4 called with value {0}",m);
}

HandlerClass():Varvalue(1){};
HandlerClass(int m):Varvalue(m){};
protected:
int Varvalue;
};

在此类中我们有两个静态函数Fun1和Fun2,两个实例函数 Fun3和Fun4,两个构造函数,一个为无参函数一个为有参函数。
声明委托对象并指向Fun1:

    Handler^ hander=gcnew Handler(HandlerClass::Fun1);

这时我们用的是委托的单个实参构造函数,它指向静态函数Fun1.

委托调用它时可以用以下两种方式:

   hander->Invoke(90)或hander(90).

为了能够将两个委托的调用列表组合成一个新的委托对象,委托类型重载了+运算符。例如用下面这条语句直观的修改hander委托的调用列表:

   hander +=gcnew Handler(HandlerClass::Fun2);

hander变量现在将引用一个调用列表包含Fun1和Fun2两个函数的委托对象,然后这是个新的对象。某个委托的列表是不能被修改的,因此+运算符的工作方式类似于处理String对象的方式即总创建一个新的对象。我们可以再次调用该委托Hander(80);会把Fun1和Fun2全部执行一次。

结果显示:

Function1 called with value 80

Function2 called with value 80

它的调用顺序和它们被添加到委托对象的顺序相同。

通过-我们可以从委托列表中有效的删除某一项。

hander -=gcnew Hander(HandlerClass::Fun1);

该语句创建一个只包含HandlerClass::Fun2的委托对象.

注意:委托的调用列表必须至少包含一个函数指针。如果我们使用减法运算符删除所有函数的指针,则结果将是nullptr.

当我们用两个形参的构造函数时,第一个实参是CLR堆上某个对象的引用,第二个实参是该对象所属的类型中某个实例函数的地址。因此,该构造函数创建的委托将包含一个由第二个实参指定 的实例函数的指针,以便供第一个实参指定的对象引用。下面是创建这种委托的方法:

  HandlerClass obj=gcnew HandlerClass;

  Handler handler2=gcnew Handler(obj,&HandlerClass::Fun3);

第一条语句创建了一个对象,第二条语句为HandlerClass对象obj创建一个指向Fun3()函数委托,该委托要求提供int类型的实参,因此我们可以用下面语句调用它: handler2(100);

// Delegate.cpp: 主项目文件。

#include "stdafx.h"

using namespace System;

public ref class HandlerClass
{
public:
static void Fun1(int m)
{
Console::WriteLine(L"Function1 called with value {0}",m);
}

static void Fun2(int m)
{
Console::WriteLine(L"Function2 called with value {0}",m);
}
void Fun3(int m)
{
Console::WriteLine(L"Function3 called with value {0}",m);
}

void Fun4(int m)
{
Console::WriteLine(L"Function4 called with value {0}",m);
}

HandlerClass():Varvalue(1){};
HandlerClass(int m):Varvalue(m){};
protected:
int Varvalue;
};

public delegate void Handler(int Value);
int main(array<System::String ^> ^args)
{
Handler^ hander=gcnew Handler(HandlerClass::Fun1);//Delegate object;

Console::WriteLine(L"Delegate with one Pointer to a static function:");
hander->Invoke(90);

hander +=gcnew Handler(HandlerClass::Fun2);
Console::WriteLine(L"\nDelegate with two pointers to static functions:");
hander->Invoke(80);

HandlerClass^ obj=gcnew HandlerClass;
Handler^ handler2=gcnew Handler(obj,&HandlerClass::Fun3);
hander +=handler2;
Console::WriteLine(L"\nShortening with three pointers to functions:");
hander(70);

Console::WriteLine(L"\n Shortening the invocation list...");
hander-=gcnew Handler(HandlerClass::Fun1);
Console::WriteLine(L"\n Delegate with pointers to one static and one instance function:");
hander(60);
Console::WriteLine(L"Hello World");
Console::ReadKey();
return 0;
}

结果为:

 

从以上看出,委托对象可以合并静态与非静态函数。

三、无约束的委托

   以上的委托的列表中都要有一组固定的函数,我们称之为有约束委托。

   无约束委托指向特定对象类型的形参列表和返回类型与委托声明一致的实例函数,因些相同的委托可以调用指定类型的任何对象的实例函数。下面为无约束委托的声明:

  public delegate void UBHandler(ThisClass^,int Value);

第一个参数指定this指针的类型,UBHandler类型的委托可以为该指针调用某个实例函数,该函数必须有int类型的单个形参,其返回类型必须是void。因此UBHandler类型的委托只能为ThisClass类型的对象调用函数,但该类型的对象可以是任意一个。

  UBHandler^ ubh=gcnew UBHandler(&ThisClass::Sum);

以下为调用:

  ThisClass^ obj=gcnew ThisClass(99.0);

   ubh(obj,5);

以下为源码:

 

// 无约束委托.cpp: 主项目文件。

#include "stdafx.h"

using namespace System;
public ref class ThisClass
{
public:
void Sum(int n)
{
Console::WriteLine(L"SumResult = {0}",Value+n);
}

void Product(int n)
{
Console::WriteLine(L"Product = {0}",Value*n);
}

ThisClass(double v):Value(v)
{

}
private:
double Value;
};

public delegate void UBHandler(ThisClass^, int n);
int main(array<System::String ^> ^args)
{
array<ThisClass^>^ things={gcnew ThisClass(5.0),
gcnew ThisClass(10.0),
gcnew ThisClass(15.0),
gcnew ThisClass(20.0),
gcnew ThisClass(25.0)
};

UBHandler^ ubh=gcnew UBHandler(&ThisClass::Sum); //创建一个委托对象
for each(ThisClass^ thing in things )
ubh(thing,3);

ubh += gcnew UBHandler(&ThisClass::Product); //给委托增加一个函数
//为things中的对象调用委托
for each(ThisClass^ thing in things)
ubh(thing,2);
Console::WriteLine(L"Hello World");
Console::ReadKey();
return 0;
}

结果:

 

从结果可以看出,才开始我们是用到了sum函数,后来又加上product函数。

四、创建事件

  前面曾说过,发出某个事件发生的信号需要使用委托,该委托应该包含该事件发生时将被调用的函数指针。我们要在程序中处理的大多数事件与按钮或菜单项这样的控件有关,这些事件源于用户与程序交互作用,但我们也可以在自己的程序代码中定义和触发事件。

事件是使用event关键字和委托类名定义的引用类成员:

public delegate void DoorHandler(String^ str);

public ref class Door

{

  public:

      event DoorHandler^ Knock;

      void TriggerEvents()

      {

          Knock("Fred");

          Knock("Jane");

       }

};

Door类中有一个名为Knock的事件成员,该事件对应于DoorHandler类型的委托,Knock是Door类的实例成员,但我们可以使用static关键字将某个事件指定为静态类成员,还可以将某个事件声明为virtual.当某个Knock事件被触发时,它可以调用具有DoorHandler委托指定的形参列表和返回类型的函数。

Door类还有一个触发两次Knock事件的公有函数TriggerEvent(),两次触发使用不同的实参。这两实参将被传递给已注册的接收Knock事件通知的函数。触发事件实质上与调用委托相同。

   我们可以像下面这样定义一个能够处理Knock事件的类:

 public ref class AnswerDoor

{

  public:

      void ImIn(String^ name)

       {

           Console::WriteLine(L"Come in {0},it's open.",name);

        }

       void ImOut(String^ name)

       {

          Console::WriteLine(L"Go away {0},I'm Out.",name);

        }

};

AnswerDoor类中有两个能处理Knock事件的公有函数成员,因为二者的形参列表和返回类型与DoorHandler委托的声明所指定的一致。

在能够注册将接收Knock事件通知的函数之前,我们需要创建一个Door对象,创建Door对象的方法如下:

   Door^ door=gcnew Door;

现在,我们可以像下面这样,注册用来接收Door对象中的Knock事件通知的函数:

  AnswerDoor^ answer=gcnew AnswerDoor;

     door->Knock +=gcnew DoorHandler(answer,&AnswerDoor::ImIn);

第一条语句是创建一个AnswerDoor类型的对象,我们需要该对象,因为ImIn和ImOut()函数不是静态类成员。然后我们将DoorHandler委托类型的一个实例与door的Knock成员相加。这完全就是给委托添加函数指针的过程,我们可以以相同的方式,添加更多的当Knock事件被触发时将被调用的事件处理函数。

以下为完整代码:

  

// 事件Event.cpp: 主项目文件。

#include "stdafx.h"

using namespace System;
public delegate void DoorHandler(String^ str);

public ref class Door
{
public:
event DoorHandler^ Knock;
void TriggerEvents()
{
Knock(L"Fred");
Knock(L"Jane");
}
};

public ref class AnswerDoor
{
public:
void ImIn(String^ name)
{
Console::WriteLine(L"Come in {0},it's open.",name);
}

void ImOut(String^ name)
{
Console::WriteLine(L"Go away {0},I'm out.",name);
}
};


int main(array<System::String ^> ^args)
{
Door^ door=gcnew Door;
AnswerDoor^ answer=gcnew AnswerDoor;

door->Knock+=gcnew DoorHandler(answer,&AnswerDoor::ImIn);//注册ImIn函数,使之接收door中的Knock事件的通知。
door->TriggerEvents();

door->Knock-=gcnew DoorHandler(answer,&AnswerDoor::ImIn);//删除ImIn函数
door->Knock+=gcnew DoorHandler(answer,&AnswerDoor::ImOut);//注册ImOut函数,使之接收door中的Knock事件的通知。
door->TriggerEvents();
Console::WriteLine(L"Hello World");
Console::ReadKey();
return 0;
}

显示结果:

  



 

 




posted on 2011-09-28 11:33  天上星  阅读(856)  评论(0编辑  收藏  举报

导航