C#委托
类似与C中的函数指针
函数指针
typedef int (*FUN)(int a,int b); // 定义函数指针类型
int max(int x,int y)
{ return x>y?x:y;}
int _tmain(int argc, _TCHAR* argv[])
{
FUN fun;
fun=(FUN)max;
int c=fun(3,5); // 或者c=(*fun)(3,5);通过指针调用函数
printf("c is %d \n",c);
return 0;
}
或者当作函数的参数:
typedef int (*FUN)(int a,int b);
int max(int x,int y){ return x>y?x:y;}
int min(int x,int y){ return x<y?x:y;}
int add(int x,int y){return x+y;}
int process(int x,int y, FUN fun){ return (*fun)(x,y);}
int _tmain(int argc, _TCHAR* argv[])
{
int c=process(3,5,max);//函数指针作为函数的参数
printf("max is %d \n",c);
c=process(3,5,min);
printf("min is %d \n",c);
c=process(3,5,add);
printf("add is %d \n",c);
……}
函数指针是非类型安全的:
int m(int a,int b,int r) {return r+1;}
fun=(FUN)m; //无意义的转换
c=fun(3,5);
printf(“c is %d \n”,c); //c返回一个随机值
委托
访问修饰符 delegate 返回值类型 委托名字(委托参数)
public delegate int DEL(int a,int b);
可以在名字空间下定义也可以在类中定义。
定义一个委托等价于定义了一个指针类型。使用之前先实例化委托。实例化时指定此委托所代表的函数,函数可以是类的静态成员函数,或者是普通的成员函数。
delegate int DEL(int a,int b);
class Class1
{ int max(int a,int b)
{return a>b?a:b;}
static int smax(int a,int b)
{return a>b?a:b;}
[STAThread]static void Main(string[] args)
{
DEL del=new DEL (new Class1().max );//实例化
DEL del2=new DEL (smax);//实例化
int c=del(3,5); //通过委托来调用函数
c=del2(6,8);
}
}
委托是类型安全的:
delegate int TDEL (int a,int b,int c);
Static int m(int a,int b,int c) {return c+1;}
DEL del=new DEL(m); //实例化委托时,函数签名与委托签名必须匹配,
TDEL tdel=new TDEL (m);//必须匹配才可实例化
DEL del=(DEL)tdel; //编译器也会阻止不匹配的转换。
为什么要使用委托呢?
首先大部分委托(包括接下来的事件)都是C#设计模式的一种思想,委托并不是独有一定“特定”功能,而是规范程序运行流程,或者说规范代码编写结构,可读性更强。委托能办到的事情,使用其他的基本方法都能办到,就相当于公司老板可以直接安排员工工作,也可以先“委托”小组组长来让小组组长给下级员工安排工作,这个时候老板是不管具体是安排的谁的,老板只需要知道事情被安排出去了。
这种机制用一个不同类型函数参数的函数重载来举例,例如如下冒泡排序:
public class Bubble
{ // 整数数组冒泡排序
static public void sort(int [] sortarr)
{for(int i=0;i<sortarr.Length ;i++)
{for (int j=0;j<i;j++)
{if(sortarr[i]<sortarr[j])
{ int temp=sortarr[i];
sortarr[i]=sortarr[j];
sortarr[j]=temp;}
}}}
}
这个时候会发现,这个冒泡排序只合适int类型的排序,如果这个时候想复用这个函数来进行其他类型的排序时,可以使用函数重载,也可以使用模板类和操作符重载来完成。
这个时候可以直接使用委托,因为我们发现让函数可以比较多种类型,只需要更改函数的比较方式就可以。
比如int用大于小于符号来比较,而字符串用compare函数来比较。
我们直接用object来打包参数,用委托来打包方法。
public delegate bool Compare(object rh,object lh);//
public class Bubble
{static public void sort(object[] sortarr,Compare cmp)
{ for(int i=0;i<sortarr.Length ;i++)
{ for (int j=0;j<i;j++)
{ if(cmp(sortarr[i],sortarr[j]))//
{ object temp=sortarr[i];
sortarr[i]=sortarr[j];
sortarr[j]=temp;}
}}}
}
此时可以说sort是“小组长”了,对于sort来说它的职责已经从“接收boss指令,执行boss指令”变成“接收boss的指令,根据需求来安排不同的员工(cmp)来执行指令”。当然,这个时候其实是对象自己来决定用什么方法进行比较的。
在真正要进行比较的对象中,只需要具体安排一下适合于对象自己的比较方法就行:
public class employee{
private float salary;
public employee(float sal){salary=sal;}
public static bool RhLessThanLh(object lh,object rh)
{return (((employee)lh).salary<((employee)rh).salary)
?true:false;}//定义一个与委托兼容的函数
public static Compare empcmp=new Compare (RhLessThanLh); //定义一个静态的委托实例,以类的静态函数去初始化它。
}
在main函数中:
employee[] employees=
{new employee (100),
new employee (80),
new employee (120)};
Bubble.sort(employees,employee.empcmp );}
多重委托
委托也可以同时包含多个方法。
delegate void DEL(int a,int b);
class Class1
{ static void max(int a,int b) {Console.WriteLine (a>b?a:b);}
static void min(int a,int b) {Console.WriteLine (a<b?a:b);}
[STAThread] static void Main(string[] args)
{ DEL maxdel=new DEL (max);
DEL mindel=new DEL (min);
DEL dels=new DEL (max);
dels+=mindel;dels(3,5);
//顺序调用max和min函数,如果函数有返回值,则只得到最后一个函数的返回值.
dels-=maxdel;
dels(3,5); //只调用min函数.
}
}
C#事件
GUI应用程序的机制——事件驱动
消息传播伪代码:
typedef struct tagMSG {
HWND hwnd; // 消息要发送到的窗口的句柄。
UINT message; //消息的唯一标识,在windows头文件中定义,以WM_为前缀
WPARAM wParam; // 32位的消息参数,确切意义取决于消息本身
LPARAM lParam; //32位的消息参数,确切意义取决于消息本身
DWORD time; //消息放入队列的时间,windows使用其来保持消息的顺序
POINT pt; // 消息放入消息队列时的鼠标坐标。
} MSG, *PMSG;
LRESULT CALLBACK WndProc(
HWND hWnd, // 窗口句柄
UINT message, // 消息标识符
WPARAM wParam, // 消息参数1
LPARAM lParam) // 消息参数2
从窗口句柄中可以得到消息的发送目的地。
从消息参数中可以提取消息的发送主体。
可以直接让编译器来生成代码框架,而不是手动用SDK来一点点搭建GUI.
vs.net ->C++ windows
相关的GUI模型
- MFC(MVC)
- VCL
C#将以上事件(消息循环)进行面向对象封装,C#对象可以发布(publish)一组事件供其他类订阅(subscribe)。
例如用鼠标点击图形化界面的某个按钮,这就是事件之一,不过事件不仅仅用于图形化界面,事件定义为“对象发送某种事情向类客户提供通知的一种方法”。
事件参数类
EventArgs
是包含事件数据类的基类——不包含事件数据
- 如果需要储存状态信息,则需要从此类派生出类来保存数据
- 事件参数类是对Windows消息的封装与推广
事件处理器
处理事件的函数
n事件发生后,处理该事件的函数称为事件处理器(event handler)。.net 规定这个函数具有如下形式:
void SecondChangeHandler(object sender, TimeInfoEventArgs fe);
//事件的发送者,任何对象都派生于object。//事件参数
事件处理器具有如上的原型,因此可以使用delegate来定义之:
public delegate void SecondChangeHandler (object clock, TimeInfoEventArgs timeInformation);
事件处理器类比于窗口过程。(Windows Procedure)
event关键字
发布事件的类以event关键字来表名此类有一个处理某种事件的处理器函数,即某delegate的实例。
event SecondChangeHandler OnSecondChange;
发布事件类
public class Clock
{ private int hour;
private int minute;
private int second;
// 定义一个委托
public delegate void SecondChangeHandler
(object clock,TimeInfoEventArgs timeInformation);
// 定义一个事件,即以上委托的实例,由订阅者添加内容.
public event SecondChangeHandler OnSecondChange;
}
public void Run()
{for(;;) //无限循环
{Thread.Sleep(10); // sleep 10 milliseconds
System.DateTime dt = System.DateTime.Now;
if (dt.Second != second) //秒改变,通知订阅者
{TimeInfoEventArgs timeInformation = new
TimeInfoEventArgs( dt.Hour,dt.Minute,dt.Second);
if (OnSecondChange != null)
OnSecondChange( this,timeInformation);
//通知订阅者。
this.second = dt.Second;
this.minute = dt.Minute;
this.hour = dt.Hour;
}
}
}
订阅事件类
决定事件到达后如何处理(事件处理器),必须与对应的delegate保持一致
public void TimeHasChanged(object theClock, TimeInfoEventArgs ti)
其次,订阅事件类必须通过某种途径告知发布事件类,自己对发布事件类感兴趣,而且将对事件的到来采取何种动作:
public void Subscribe(Clock theClock)
{ theClock.OnSecondChange +=
new Clock.SecondChangeHandler(TimeHasChanged);
//订阅事件类的其他方法
//新建一个发布事件类所定义的委托,初始化为自己的处理器,并赋值给发布事件类的事件。
//事件类的事件中可能存放有不同的订阅类的处理器,这是一个多重委托,事件发生时会按照订阅次序逐个通知他们.
}
卢老师给的示例:
public class DisplayClock
{public void Subscribe(Clock theClock)
{ theClock.OnSecondChange +=
new Clock.SecondChangeHandler(TimeHasChanged);
} //也可以在订阅类的构造函数中实现。
public void TimeHasChanged(
object theClock, TimeInfoEventArgs ti)
{ Console.WriteLine("Current Time: {0}:{1}:{2}",
ti.hour.ToString(),
ti.minute.ToString(),
ti.second.ToString());
}
}
public class LogCurrentTime
{ public void Subscribe(Clock theClock)
{ theClock.OnSecondChange +=
new Clock.SecondChangeHandler(WriteLogEntry);
}
public void WriteLogEntry(
object theClock, TimeInfoEventArgs ti)
{ Console.WriteLine("Logging to file: {0}:{1}:{2}",
ti.hour.ToString(),
ti.minute.ToString(),
ti.second.ToString());
}
}
public class Test
{ public static void Main()
{ Clock theClock = new Clock(); //发布事件类
DisplayClock dc = new DisplayClock();//订阅类
dc.Subscribe( theClock );//订阅
LogCurrentTime lct = new LogCurrentTime(); //另一订阅类
lct.Subscribe( theClock );//订阅
theClock.Run();
}
}