委托是什么?

(1) 从数据结构来讲,委托是和类一样是一种用户自定义类型。

(2) 从设计模式来讲,委托(类)提供了方法(对象)的抽象。

既然委托是一种类型,那么它存储的是什么数据?
我们知道,委托是方法的抽象,它存储的就是一系列具有相同签名和返回回类型的方法的地址。调用委托的时候,委托包含的所有方法将被执行。

委托类型的定义

委托是类型,就好像类是类型一样。与类一样,委托类型必须在被用来创建变量以及类型对象之前声明。

delegate void MyDel(int x);

委托类型声明:

(1) 以deleagate关键字开头。

(2)返回类型+委托类型名+参数列表。

3. 声明委托变量

MyDel del1,del2;

初始化委托变量

(1) 使用new运算符

new运算符的操作数的组成如下:

1
2
del1 = new MyDel( myInstObj.MyM1 );
del2 = new MyDel( SClass.OtherM2 );

方法可以是实例方法或静态方法

(2)使用快捷语法

快键语法,它仅由方法说明符构成。之所以能这样,是因为在方法名称和其相应的委托类型之间有隐式转换。

1
2
del1 = myInstObj.MyM1;
del2 = SClass.OtherM2;

赋值委托

由于委托是引用类型,我们可以通过给它赋值来改变包含在委托变量中的方法地址引用。旧的引用会被垃圾回收器回收。

1
2
3
MyDel del;
del = myInstaObj.MyM1; //委托初始化
del = SClass.OtherM2;//委托重新赋值,旧的引用将被回收

组合委托

委托可以使用额外的运算符来组合。这个运算最终会创建一个新的委托,其调用列表是两个操作数的委托调用列表的副本的连接。

委托是恒定的,操作数委托创建后不会被改变。委托组合拷贝的是操作数的副本。

1
2
3
MyDel del1 = myObj.MyMethod;
MyDel del2 = SClass.OtherM2;
MyDel del3 = del1 + del2; //组合调用列表

委托加减运算

可以使用+=运算符,为委托新增方法。

同样可以使用-=运算符,为委托移除方法。

1
2
3
MyDel del = myObj.MyMethod;
del += SClass.OtherM2; // 增加方法
del -= myObj.MyMethod; // 移除方法

委托调用

委托调用跟方法调用类似。委托调用后,调用列表的每个方法将会被执行。

在调用委托前,应判断委托是否为空。调用空委托会抛出异常。

1
2
3
4
if(null != del)
{
del();//委托调用
}

匿名方法

匿名方法是在初始化委托时内联声明的方法。

基本结构:

1
deleage( 参数 ) { 语句块 }

例如:

1
2
3
delegate int MyDel (int x); //定义一个委托

MyDel del = delegate( int x){ return x; };

从上面我们可以看到,匿名方法是不会显示声明返回值的。

Lambda表达式

Lambda表达式主要用来简化匿名方法的语法。在匿名方法中,delegate关键字有点多余,因为编译器已经知道我们将方法赋值给委托。通过几个简单步骤,我们就可以将匿名方法转换为Lambda表达式:

1
2
3
MyDel del = delegate( int x) { return x; };//匿名方法
MyDel del2 = (int x) => {return x;};//Lambda表达式
MyDel del3 = x => {return x};//简写的Lambda表达式

简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Program
{
private delegate void MyDel(string text);
static void Main(string[] args)
{
MyDel myDel=ConsoleWriteTest;
myDel("1");//调用委托
DelegateParaTest("2",myDel);//委托作为参数
DelegateParaTest("3",ConsoleWriteTest);//方法隐式转换为委托
DelegateParaTest("4",x=>Console.WriteLine(x));
Console.ReadLine();

}

private static void ConsoleWriteTest(string text)
{
Console.WriteLine(text);
}

private static void DelegateParaTest(string text,MyDel mydel)
{
mydel(text);
}
}

注意

使用new初始化委托的时候必须包含参数,参数可以是委托实例,方法,Lambda表达式。

委托是和类一样是一种用户自定义类型:

delegate void MyDel(int x);很容易把委托MyDel当成一个方法或者属性。

private MyDel _myDel;这才是一个委托类型的属性

一个委托类型作为类属性的时候,使用+=之前不用进行初始化(不用new或者=进行赋值),但是作为方法内一个变量的时候,必须进行初始化。

1
2
3
4
5
6
7
8
9
10
11
12
private delegate void MyDel(string text);
private static MyDel _myDel;
static void Main(string[] args)
{
_myDel += Console.WriteLine;
_myDel("233");
//MyDel myDel = Console.WriteLine;//报错
MyDel myDel = Console.WriteLine;
myDel("666");
Console.ReadLine();

}

事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public delegate void MyDel(string text);
class MyClass
{

public MyDel MyDel;
public void InvokeMyDel(string text)
{
if (MyDel != null)
{
MyDel(text);
}
}

}

类中有一个委托类型的属性。可以这样使用

1
2
3
4
MyClass myClass=new MyClass();
myClass.MyDel = Console.WriteLine;
myClass.InvokeMyDel("1");
myClass.myDel("1");//也可以直接调用,但是调用之前判断是否为null

可以为委托增加多个方法

1
2
3
4
MyClass myClass=new MyClass();
myClass.MyDel = Console.WriteLine;
myClass.MyDel += Console.WriteLine;
myClass.InvokeMyDel("1");

因为要在委托上增加方法,声明为public,但是如果一不小心使用了=进行重新赋值(特别是当委托属性是static的时候),就会造成意向不到的结果。为了避免这一情况,使用private修饰委托属性,公开一个方法在委托属性上增加方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyClass
{

public delegate void MyDel(string text);

private MyDel myDel;
public void InvokeMyDel(string text)
{
if (myDel != null)
{
myDel(text);
}
}

public void AddMethod(MyDel method)
{
myDel += method;
}

}

事件是一种特殊的委托的实例,或者说是受限制的委托,是委托一种特殊应用,在类的外部只能施加+=,-=操作符,二者本质上是一个东西。

event MyDel _myDel; // 编译成创建一个私有的委托示例, 和施加在其上的add, remove方法.

event只允许用add, remove方法来操作,这导致了它不允许在类的外部被直接触发,只能在类的内部适合的时机触发。委托可以在外部被触发,但是别这么用。

使用中,委托常用来表达回调,事件表达外发的接口。

委托和事件支持静态方法和成员方法, delegate(void * pthis, f_ptr), 支持静态返方法时, pthis传null.支持成员方法时, pthis传被通知的对象.

委托对象里的三个重要字段是, pthis, f_ptr, pnext, 也就是被通知对象引用, 函数指针/地址, 委托链表的下一个委托节点.

1
2
private MyDel _myDel;//委托
private event MyDel _myDel;//事件

事件不能在类的外部直接调用,只能通过暴露的方法进行间接触发。