委托和类一样,是一种用户定义类型(即是一种类,所以也是一个引用类型)。在它们组成的结构方面区别是,类表示的是数据和方法的集合,而委托则持有一个或多个方法。
可以把 delegate 看作是一个包含有序方法列表的对象,这些方法具有相同的签名和返回类型。
(1)方法的列表称为调用列表。
(2)委托持有的方法可以来自任何类或结构,只要它们在下面两方法匹配:
(3)调用列表中的方法可以是实例方法也可以是静态方法。
(4)在调用委托的时候,会执行其调用列表中的所有方法。
声明委托代码:
delegate void MyDel(int x);
//delegate:关键字
//void:返回类型
//MyDel:委托类型名称
//MyDel(int x):签名
委托与方法在声明时有两点不同:
说明: 虽然委托类型声明看上去和方法的声明一样,但它不需要在类内部声明,因为它是类型声明。
委托类型的变量声明:
MyDel delVar;
//MyDel:委托类型
//delVar:变量
有两种创建委托对象的方法:
(1)使用带 new 运算符的对象创建表达式。
new 运算符的操作数的组成:
假设 myInstObj 是类对象,MyM1 是 myInstObj 的一个实例方法。 SCLass 是类,OtherM2 是 SCLass 的静态方法。
//创建委托并保存引用
delVar = new MyDel(myInstObj.MyM1);
delVar = new MyDel(SCLass.OtherM2);
(2)快捷语法
仅由方法说明符构造。这种快捷语法能够工作是因为在方法名称和其相应的委托类型之间存在隐式转换。
//创建委托并保存引用
delVar = myInstObj.MyM1;
delVar = SClass.OhterM2;
以下是创建委托对象的完整代码:
delegate void MyDel(int x);
//使用 new 运算符方式
MyDel delVar,dVar;
delVar = new MyDel(myInstObj.MyM1);
dVar = new MyDel(SClass.OtherM2);
//快捷语法方式
MyDel delVar = myInstObj.MyM1;
MyDel dVar = SClass.OhterM2;
除了为委托分配内存,创建委托对象还会把第一个方法放入委托的调用列表。
由于委托是引用类型,我们可以通过给它赋值来改变包含在委托变量中的引用。旧的委托对象被垃圾回收器回收。
MyDel delVar;
delVar = myInstObj.MyM1;
delVar = SClass.OhterM2;
委托也可以包含多个方法。
也称为多播委托。
比如,创建了3个委托。其中第3个委托由前两个委托组合而成。
MyDel delA = myInstObj.MyM1;
MyDel delB = SClass.OhterM2;
MyDel delC= delA + delB;//组合调用列表
委托是恒定的,不会因为组合而改变原委托。委托对象被创建后不能再被改变。
使用 += 运算符来添加方法。
MyDel delVar = inst.MyM1;
//增加两个方法
delVar += SCl.m3;
delVar += X.Act;
在使用 += 运算符时,实际发生的是创建了一个新的委托,其调用列表是左边的委托加上右边方法的组合。然后将这个新的委托赋值给 delVar。
(我有疑问: 委托是可以存放多个方法的方法列表,那为什么还要先创建一个新的委托,再赋值给 delVar 委托对象呢?这里的赋值是不是指在原有的基础上再增加一个元素(方法)的意思?比如类似于string str = “a”; str += “bc”; 【temp = str+“bc”; 把 temp 赋值到 str 当中】 )
使用 -= 运算符从委托移除方法。(在这里我似乎理解了事件为什么还能跟委托有一定的关联,此处写法很像是事件的订阅和取消订阅的工作方式)
//从委托移除方法
delVar -= SCl.m3;
与为委托添加方法一样,其实是创建了一个新的委托。新的委托是旧委托的副本——只是没有了已经被移除方法的引用。(加强理解: 比如 string str = “abc”; 去除了"bc"后得出一个新值 temp = “a”,然后再把 temp 赋值给 str。)
移除委托时需要记住的一些事项
调用委托需要知道的重要事项:
代码例子:
class TestClass
{
static public void Fun(string str)
{
Console.WriteLine(str);
}
}
class Program
{
delegate void MyDel(string str);
static void Main(string[] args)
{
MyDel delVar = TestClass.Fun;
//又增加了该方法,于是调用列表出现两次该方法
delVar += TestClass.Fun;
delVar.Invoke("调用Fun");
delVar-= TestClass.Fun;
//移除一次后,还剩一个 TestClass.Fun
delVar.Invoke("移除一次该方法后,调用Fun");
Console.ReadKey();
}
}
输出结果:
调用Fun
调用Fun
移除一次该方法后,调用Fun
本书的示例:
delegate void PrintFunction();
class Test
{
public void Print1() { Console.WriteLine("Print1 -- instance"); }
public static void Print2() { Console.WriteLine("Print2 -- static"); }
}
class Program
{
static void Main(string[] args)
{
Test t = new Test();
PrintFunction pf;
pf = t.Print1;
//给委托增加3个另外的方法
pf += Test.Print2;
pf += t.Print1;
pf += Test.Print2;
//现在,委托含有4个方法
if (null != pf)
pf();
else
Console.WriteLine("Delegate is empty.");
Console.ReadKey();
}
}
输出结果:
Print1 – instance
Print2 – static
Print1 – instance
Print2 – static
如果委托有返回值并且在调用列表中有一个以上的方法,会发生如下的情况:
class MyClass
{
int IntValue = 5;
public int Add2() { IntValue += 2; return IntValue; }
public int Add3() { IntValue += 3; return IntValue; }
//为了加强理解书中的内容,自己添加的方法Add4
public int Add4() { return 1234; }
}
class Program
{
static void Main(string[] args)
{
MyClass mc = new MyClass();
MyDel mDel = mc.Add2;//返回值为7被忽略
//为了验证输出结果是否为最后一个方法返回的结果
//mDel += mc.Add4;
mDel += mc.Add3;//返回值为10被忽略
mDel += mc.Add2;
//mDel += mc.Add4;//若执行该语句,则输出内容为1234
Console.WriteLine($"Value:{ mDel() }");
Console.ReadKey();
}
}
输出结果:
Value:12
如果委托有引用参数,参数值会根据调用列表中的一个或多个方法的返回值而改变。
class MyClass
{
public void Add2(ref int x) { x += 2; }
public void Add3(ref int x) { x += 3; }
}
class Program
{
static void Main(string[] args)
{
MyClass mc = new MyClass();
MyDel mDel = mc.Add2;
mDel += mc.Add3;
mDel += mc.Add2;
int x = 5;
mDel(ref x);
Console.WriteLine($"Value:{ x }");
Console.ReadKey();
}
}
输出结果:
Value:12
匿名方法是在实例化委托时内联声明的方法。
使用条件: 如果方法只会被使用一次——用来实例化委托,则使用匿名方法来处理。
在如下地方使用匿名方法:
匿名方法的语法
匿名方法表达式的语法包含如下组成部分:
delegate (Parameters){ ImplementationCode }
//delegate:关键字
//Parameters:参数列表
//ImplementationCode:语句块
1)返回类型
匿名方法不会显式声明返回值。
delegate int OhterDel(int InParam);
static void Main()
{
OtherDel del = delegate(int x)
{
retrun x + 20;
};
}
2)参数
除了数组参数,匿名方法的参数列表必须在如下3方面与委托匹配:
可以通过使圆括号为空或省略圆括号来简化匿名方法的参数列表,但必须满足以下两个条件:
delegate void SomeDel(int x);
SomeDel SDel = delegate
{
PrintMessage();
Cleanup();
};
不懂:如果不使用参数的话,怎么对参数进行操作呢?这不就是相当于无参数的委托么?
3)params 参数
如果委托声明的参数列表包含了 params 参数,那么匿名方法的参数列表将忽略 params 关键字。
delegate void SomeDel(int X,params int[] Y);
SomeDel mDel = delegate(int X,int Y)
{
};
delegate void MyDel(int x);
class Program
{
static void Main(string[] args)
{
MyDel mDel = delegate (int y)
{
int z = 10;
Console.WriteLine("{0},(1)", y, z);
//y,z 的作用域在花括号内
};
Console.WriteLine("{0},(1)", y, z);//编译错误
Console.ReadKey();
}
}
外部变量
Lambda 表达式简化了匿名方法的语句。
把匿名方法转换为 Lambda 表达式:
MyDel del = delegate(int x){ return x + 1; }; //匿名方法
MyDel le1 = (int x) => { return x + 1; }; //Lambda 表达式
进一步简化 Lambda 表达式:
委托参数
如果只有一个隐式类型参数,可以省略两端的圆括号。
Lambda 表达式的主体是语句块或表达式,如果语句块包含了一个返回值语句,可以将语句块替换为 return 关键字后的表达式。(即 { return x + 1 ; }; 替换为 x + 1 ; )
//匿名方法
MyDel del = delegate(int x){ return x + 1; } ;
// Lambda 表达式
MyDel le1 = (int x) => { return x + 1; } ;
MyDel le2 = (x) => { return x + 1; } ;
MyDel le3 = x => { return x + 1; } ;
MyDel le4 = x => x + 1 ;
有关 Lambda 表达式的参数列表的要点: