文章不定时更新,有问题的话欢迎各位dalao指正。
什么是委托
委托是拥有一个或多个方法的引用类型,委托可以将可执行的代码从一个方法传递到另一个方法。
官方文档中说:委托表示对具有特定参数列表和返回类型的方法的引用
看下面这个例子:
//第一步、声明委托类型
delegate void MyDel(int Value);
class Program
{
void oodNumber(int Value) {
Console.WriteLine($"{Value} is ood Number!");
}
void evenNumber(int Value) {
Console.WriteLine($"{Value} is even Number!");
}
static void Main(string[] args)
{
Program program = new Program();
//2、声明委托变量
MyDel myDel;
//创建一个随机数,范围在0到99之间
Random random = new Random();
int randomValue = random.Next(99);
//3、创建委托对象,将符合条件的委托对象赋值给myDel变量
myDel =(randomValue % 2 == 0)
? new MyDel(program.evenNumber)
: new MyDel(program.oodNumber);
//4、调用委托
myDel(randomValue);
}
}
上面的代码可以对随机生成的0到99之间的整数进行判断,判断该整数是奇数还是偶数。
输出如下:
42 is even Number!
上面的代码中,
第二步声明的委托变量myDel,这只是产生了一个委托对象的引用,而并没有创建对象,只是创建了一个含有委托对象引用的变量。
在第三步中,如果随机数的值为偶数,那么会创建program.evenNumber这个委托对象,然后将其赋值给这个委托变量myDel。
第四部调用委托的时候,委托变量myDel的持有方法(evenNumber或oodNumber)被执行
委托的概述
官文中这样描述:
委托具有以下属性:
- 委托类似于 C++ 函数指针,但委托完全面向对象,不像 C++ 指针会记住函数,委托会同时封装对象实例和方法。
- 委托允许将方法作为参数进行传递。
- 委托可用于定义回调方法。
- 委托可以链接在一起;例如,可以对一个事件调用多个方法。
- 方法不必与委托类型完全匹配。 (变体:协变,逆变)
- 使用 Lambda 表达式可以更简练地编写内联代码块。 Lambda 表达式(在某些上下文中)可编译为委托类型。
具体的用法会在之后的文章中说明,该文章讲解基础用法。
委托和类一样,是用户自定义的类型,但类表示的是数据和方法的集合,而委托则持有一个或多个方法,以及一系列预定义操作。
使用委托的操作步骤:
- 1、声明委托类型
- 2、使用1中声明的委托类型声明一个委托变量
- 3、创建委托类型的对象,把它赋值给委托变量
- 4、可以给委托对象增加其他的方法
- 5、调用委托
我们可以把delegate看做一个包含有序方法列表的对象,这些方法具有相同的签名(参数)和返回类型
方法的列表成为调用列表
委托保存的方法可以来自任何类或结构,但需要满足
(1)委托的返回类型与方法的返回类型相匹配
(2)委托的签名与方法的签名相匹配(包括ref和out关键字,这两个关键字会在后面的文章进行补充说明)
- 委托{
调用列表{方法1、方法2、…}
}
声明委托类型
格式:
delegate void MyDel ( int x );
delegate为声明委托的关键字,
void为返回类型,
MyDel为委托的类型名,
括号里面包含的叫做签名。
其中返回类型和签名指定了委托接受方法的形式。
委托虽然看上去和方法的声明相同,但是他不需要在类的内部进行声明,因为它是类型声明
创建委托对象
格式:
MyDel delVar;
MyDel是刚才声明的委托类型,
delVar是声明的变量。
创建委托对象的两种方式:
我们先声明两个方法,一个是实例方法,一个是静态方法:
class myInstObj
{
public void MyM1(int x)
{
Console.WriteLine("实例方法MyM1被调用");
}
}
static class SClass
{
public static void OtherM2(int x)
{
Console.WriteLine("实例方法OtherM2被调用");
}
}
第一种方法:使用new运算符的对象创建表达式
MyDel delVar;
myInstObj m = new myInstObj();
delVar = new MyDel(m.MyM1);
m.MyM1是实例方法
MyDel deVar;
deVar = new MyDel(SClass.OtherM2);
SClass.OtherM2是静态方法
第二种,快捷语法方式:与上面的方式等价
MyDel delVar;
myInstObj m = new myInstObj();
delVar = m.MyM1;
MyDel deVar;
deVar = SClass.OtherM2;
使用这种快捷语法是因为方法名称和其相应的委托类型之间存在隐式转换。
声明多个委托变量的时候可以以,分隔:
MyDel delVar,deVar;
我们可以使用初始化语法在同一条语句中创建变量和初始化对象:
myInstObj m = new myInstObj();
MyDel delVar = new MyDel( m.MyM1 );
MyDel deVar = new MyDel( SClass.OtherM2 );
再使用快捷语法:
myInstObj m = new myInstObj();
MyDel delVar = m.MyM1;
MyDel deVar = SClass.OtherM2 ;
这种快捷语法的方式可以减少很多的代码量
给委托赋值
MyDel delVar;
delVar = m.MyM1;
delVar = SClass.OtherM2;
在上面的代码中,我们给委托变量多次赋值,但是实际上,只有最后赋值的委托对象才能被委托变量引用,而旧的委托对象会被垃圾回收器回收。(也就是说委托变量只会引用新赋值的委托变量)
组合委托
上面的委托中,在委托的调用列表中都只有一个方法,而委托是可以使用运算符进行组合委托的:
MyDel delA = m.MyM1;
MyDel delB = SClass.OtherM2;
//组合调用列表
MyDel delC = delA + delB;
委托是恒定的,委托对象在被创建之后不能再改变
上面的代码中只是在delC中创建了具有组合调用列表的新委托,而delA和delB未改变
给委托添加方法
我们创建一个类
class Inst
{
public void m1(int v)
{
Console.WriteLine("m1");
}
public void m2(int v)
{
Console.WriteLine("m2");
}
public void m3(int v)
{
Console.WriteLine("m3");
}
}
Inst inst = new Inst();
MyDel delVar = inst.m1;
delVar += inst.m2;
delVar += inst.m3;
上面的代码中使用+=运算符给委托添加了两个方法,而实际上,由于委托是恒定的所以委托调用列表中添加三个方法后的结果实际上是变量指向了一个全新的委托。
在+=时,实际上是创建了一个新的委托,调用列表是+=号左边 的委托加上右面方法的组合,然后把新的委托赋值给delVar。
每次添加方法,都会在委托的调用列表中创建一个新的元素。
从委托中移除方法
delVar -= inst.m3;
与添加方法相同,实际上也是创建新委托,而新的委托是旧委托的copy版,只是没有已经被移除方法的引用。
在-=时需要注意,是从掉用哪个列表的最后一个开始向前搜索,删除第一个搜索到的方法,如果删除空委托则会抛出异常。
调用委托
MyDel delVar = inst.m1;
delVar += inst.m2;
delVar += inst.m3;
delVar(1);
输出
m1
m2
m3
如果一个方法在调用列表中出现多次,当委托被调用时,每次在列表中遇到这个方法他都会被调用一次。
MyDel delVar = inst.m1;
delVar += inst.m2;
delVar += inst.m1;
delVar(1);
输出
m1
m2
m1
调用带返回值的委托
如果委托中有返回值并且在调用列表中有一个以上的方法,调用列表中最后一个方法返回的值就是委托调用返回的值,调用列表中所有其他方法的返回值都会被忽略
调用带引用参数的委托
先介绍一下ref:
在方法的参数列表中使用 ref 关键字时,它指示参数按引用传递,而非按值传递。
ref 关键字让形参成为实参的别名,这必须是变量。 换而言之,对形参执行的任何操作都是对实参执行的。
比如:
class A {
public void add(ref int a)
{
a++;
}
}
class reftest
{
static void Main()
{
A a = new A();
int b = 0;
a.add(ref b);
Console.WriteLine(b);
}
}
输出:
1
可以看到从add方法中传进去的参数是实参,在add方法中的b++改变的原本b的值。
使用ref参数的时候,方法定义和调用方法必须都使用ref关键字
介绍完ref的用法之后,再来看带有引用参数的委托:
delegate void MyDel(ref int X);
class reftest
{
public void Add2(ref int x) { x += 2; }
public void Add3(ref int x) { x += 3; }
static void Main()
{
reftest re = new reftest();
MyDel md = re.Add2;
md += re.Add3;
md += re.Add2;
int x = 5;
md(ref x);
Console.WriteLine($"Value:{x}");
}
}
输出:
Value:12
在调用委托列表中的下一个方法时,参数的新值(不是初始值)会传给下一个方法。
匿名方法
匿名方法是在初始化委托的时候内联(inline)声明的方法
对于方法只会被使用一次的委托,可以使用匿名方法来简化写法。
正常写法:
class NoName
{
public static int Add20(int x)
{
return x + 20;
}
delegate int OtherDel(int InParam);
static void Main()
{
OtherDel del = Add20;
Console.WriteLine($"{del(5)}");
Console.WriteLine($"{del(6)}");
}
}
匿名方法:
class NoName
{
delegate int OtherDel(int InParam);
static void Main()
{
OtherDel del = delegate(int x) { return x + 20; };
Console.WriteLine($"{del(5)}");
Console.WriteLine($"{del(6)}");
}
}
二者输出相同
25
26
使用匿名方法
- 声明委托变量时作为初始化表达式
- 组合委托时在赋值语句的右边
- 为委托增加事件时在赋值语句的右边
语法
delegate (Parameters) {ImplementationCode}
delegate:关键字
Parameters:参数列表
ImplementationCode:语句块
关于params参数:
如果委托声明的参数列表中包含了params参数,那么匿名方法的参数列表将忽略params关键字,也就是说在声明的时候可以省略params。
lambda表达式
在匿名方法语句中delegate关键字是可以省略的,我们可以将匿名方法转换成lambda表达式:
MyDel del = delegate(int x){return x+1;};//匿名方法
MyDel del = (int x)=>{return x+1;};//lambda
语句变得更加简洁,而lambda表达式还可以进一步进行简化:
MyDel del = x=>{return x+1;};
lambda可以让我们省略类型参数(int),使其变为隐式类型(省略类型的参数列表);
如果只有一个隐式类型参数,我们可以省略参数周围的圆括号;
同时如果语句中只包含一个返回语句,我们可以把箭头后面的语句块替换为返回值表达式;
MyDel del = x => x+1 ;
参考书籍《C#图解教程》。