C#沉淀之委托的深入讲解
什么是委托
要传递方法,就必须把方法的细节封装在一钟新类型的对象中,即委托。委托是一种特殊类型的对象,其特殊之处在于,我们以前定义的所有对象都包含数据,而委托只包含一个或多个方法的地址。
.NET版本中,委托指向方法的地址。在C++中,函数指针是一个指向内存位置的指针,但它不是类型安全的。开发者无法判断这个指针实际指向什么,像参数和返回值等项就更不知道了。
.NET委托是类型安全的类,它定义了返回类型和参数的类型。委托类不仅包含对方法的引用,也可以包含对多个方法的引用。
可以认为委托是持有一个或多个方法的对象。委托可以被执行,执行委托时委托会执行它所“持有”的方法
代码示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForDelegate { //使用关键字delegate声明委托类型 //委托是一种类型,所以它与类属于同一级别 //注意:这里是委托类型,而不是委托对象 delegate void MyDel(int value); class Program { void PrintLow(int value) { Console.WriteLine("{0} - Low Value", value); } void PrintHigh(int value) { Console.WriteLine("{0} - High Value", value); } static void Main(string[] args) { //实例化Program类,以访问PrintLow和PrintHigh方法 Program program = new Program(); //声明一个MyDel类型的委托 MyDel del; //创建随机数 Random rand = new Random(); int randomvalue = rand.Next(99); //使用三目运算符根据当前随机数的值来创建委托对象 del = randomvalue < 50 ? new MyDel(program.PrintLow) : new MyDel(program.PrintHigh); //执行委托 del(randomvalue); Console.ReadKey(); } } }
从上例可以看出,使用委托的首先得通过关键字delegate声明一个委托类型,这个委托类型包括返回值、名称、签名;当类型声明好以后,需要通过new来创建委托对象,创建对象时的参数是一个方法,这个方法的签名和返回类型必须与该委托类型定义的签名一致;调用委托时,直接通过实例化的委托对象名,并提供参数即可,然后委托会执行在其所持有的方法
委托与类
委托和类一样,是一种用户自定义的类型;不同的是类表示的是数据和方法的集合,而委托持有一个或多个方法,以及一系列预定义操作
委托的使用步骤
- 声明一个委托类型
- 使用该委托类型声明一个委托变量
- 创建委托类型的对象,把它赋值给委托变量;委托对象中包括指向某个方法的引用,此方法和委托类型定义的签名与返回类型需要一致
- 增加更多的方法(可选)
- 像调用方法一样调用委托(委托中的包含的每一个方法都会被执行)
delegate的原则
delegate相当于一个包含有序方法列表的对象,这些方法都具有相同的签名和返回类型
方法的列表称为调用列表
委托保存的方法可以来自任何类或结构,只要它们在以下两点匹配:
- 委托的返回类型
- 委托的签名(包括ref和out修饰符)
调用列表中的方法可以是静态方法也可以是实例方法
在调用委托的时候,会调用列表中的所有方法
声明委托类型
如下,delegate关键字开关,然后是返回类型,再定义名称与签名
delegate void MyDel(int vallue);
返回类型与签名指定了委托接受的方法形式
注意:委托类型是没有方法主体的
创建委托对象
使用new运算符创建对象
MyDel del = new MyDel(object.Func); //object.Func是个实例方法 Mydel _del = new MyDel(Object.Func); //Object.Func是个静态方法
使用快捷语法创建对象
MyDel del = object.Func; //object.Func是个实例方法 Mydel _del = Object.Func; //Object.Func是个静态方法
这种语法是能够工作是因为在方法名称和其相应的委托类型之间存在隐式的转换
创建委托对象后会将指定的方法加入到委托的调用列表中
由于委托是引用类型,可以通过赋值来改变包含在委托变量中的引用,如下:
MyDel del; del = new MyDel(object.FuncA); //创建第一个对象 del = new MyDel(object.FuncB); //创建第二个对象
由于第二个对象也赋值给了变量del,因此del所引用的第一个对象将被垃圾回收器回收
组合委托
//创建两个委托 MyDel del_A = new MyDel(object.FuncA); Mydel del_B = new MyDel(object.FuncA); //组合委托 MyDel del_C = del_A + del_B;
当将del_A与del_B通过+进行组合后,会返回一个新的委托对象,该对象将del_A与del_B中的方法调用列表组合到新的对象里,该新对象赋值给变量del_C,所以执行del_C的时候,会执行del_A与del_B中所保存的方法object.FuncA和object.FuncA
委托添加多个方法
MyDel del = object.FuncA; //创建并初始化委托对象 del += object.FuncB; //增加方法 del += object.FuncC; //增加方法
通过+=符号为委托对象添加更多方法,上例中,del对象不保存了三个方法,在执行del时,这三个方法会被依次调用
注意,在使用+=为委托对象添加新的方法时,实际上是创建了一个新的委托对象(原对象的副本)
移除委托方法
del -= object.FuncB; //移除方法 del -= object.FuncC; //移除方法
通过-=来将委托调用列表中已保存的方法,移除动作是从调用列表的最后一个方法开始匹配,一次只会移除一条匹配的方法,如果调用列表中不存在该方法,则没有任何效果;如果试图调用一个空的委托则会发生异常
注意,在使用-=为委托对象移除方法时,实际上是创建一个新的委托对象(原对象的副本)
调用委托
调用委托就像调用方法一样
示例:MyDel类型参考上面的定义
MyDel del = object.FuncA; //创建并初始化委托对象 del += object.FuncB; //增加方法 del += object.FuncC; //增加方法 //调用委托 del(55);
参数55会在调用委托对象时依次传递给保存的方法
一个完整的委托示例代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForDelegate { class Program { //定义委托类型 delegate void PrintFunction(string txt); //测试类中定义三个方法 class Test { public void PrintA(string txt) { Console.WriteLine("printA:{0}", txt); } public void PrintB(string txt) { Console.WriteLine("printB:{0}", txt); } public static void PrintC(string txt) { Console.WriteLine("printC:{0}", txt); } } static void Main(string[] args) { Test test = new Test(); PrintFunction pf; //实例化并创建委托对象 pf = test.PrintA; //为委托对象增加方法 pf += test.PrintB; pf += Test.PrintC; pf += test.PrintA; //添加一个重复的方法 //通过与null比较,确认委托对象中保存了方法 if (pf != null) pf("Hello"); else Console.WriteLine("pf是个空委托!"); Console.ReadKey(); } } }
调用带有返回值的委托
如何委托有返回值,并且调用列表中有一个以上的方法,那么将使用最后一个方法的返回值,之前方法的返回值被忽略
示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForDelegate { class Program { //定义委托类型 delegate int DelFunction(); //测试类中定义三个方法 class Test { int IntValue = 0; public int FuncA() { return IntValue += 1; } public int FuncB() { return IntValue += 10; } } static void Main(string[] args) { Test test = new Test(); DelFunction df; df = test.FuncA; df += test.FuncB; //最终返回值的是11 if (df != null) Console.WriteLine("返回值:"+df()); else Console.WriteLine("pf是个空委托!"); Console.ReadKey(); } } }
具有引用参数的委托
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForDelegate { //定义委托类型 delegate void MyDel(ref int x); class Program { static void Add1(ref int x) { x += 1; } static void Add2(ref int x) { x += 2; } static void Main(string[] args) { Program program = new Program(); MyDel del = Add1; del += Add2; //ref会将x当作引用值传递给委托方法 int x = 5; del(ref x); Console.ReadKey(); } } }
在调用Add1方法时,x = 5+1,再调用Add2方法时,不是x = 5+2而是x = 6 +2
参考:ref按引用传递参数
在方法的参数列表中使用 ref 关键字时,它指示参数按引用传递,而非按值传递。 按引用传递的效果是,对所调用方法中参数进行的任何更改都反映在调用方法中。 例如,如果调用方传递本地变量表达式或数组元素访问表达式,所调用方法会替换 ref 参数引用的对象,然后,当该方法返回时,调用方的本地变量或数组元素将开始引用新对象
匿名方法
匿名方法是在初始化委托时内联声明的方法
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForDelegate { //定义委托类型 delegate void MyDel(ref int x); class Program { static void Add1(ref int x) { x += 1; } static void Add2(ref int x) { x += 2; } static void Main(string[] args) { Program program = new Program(); //采用匿名方法形式代替具名方法 MyDel del = delegate(ref int y) { y += 3; }; del += Add1; del += Add2; //ref会将x当作引用值传递给委托方法 int x = 5; del(ref x); Console.ReadKey(); } } }
在声明委托变量时作为初始化表达式,或在为委托增加事件时使用
语法解析
以关键字delegate开头;后跟小括号提供参数;再后跟{}作为语句块
delegate (Parameters) {ImplementationCode}
- 匿名方法不会显示的声明返回类型delegate (int x) { return x;}即为返回一个int类型的值
- 参数的数量、位置、类型、修饰符必须与委托相匹配
- 可以通过省略圆括号或使圆括号为空来简化匿名方法的参数列表,前提是参数是不包含out参数,方法体中不使用任何参数
示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForDelegate { //定义委托类型 delegate void MyDel(ref int x); class Program { static void Add1(ref int x) { x += 1; } static void Add2(ref int x) { x += 2; } static void Main(string[] args) { Program program = new Program(); //采用匿名方法形式代替具名方法 MyDel del = delegate(ref int y) { y += 3; }; del += Add1; del += Add2; //匿名方法未使用任何参数,简化形式 del += delegate{int z = 10;}; //ref会将x当作引用值传递给委托方法 int x = 5; del(ref x); Console.ReadKey(); } } }
如果定义一个带有params形式的参数,在使用匿名方法的时候可以省略params关键字以简化代码
示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForDelegate { //定义一个带有params形式参数的委托类型 delegate void DelFunction(int x, params int[] z); class Program { static void Main(string[] args) { Program program = new Program(); // 关键字params被忽略(省略关键字以简化) DelFunction df = delegate(int x, int[] y) { ... }; Console.ReadKey(); } } }
Lambda表达式
Lambda可以简化匿名方法,语法形式如下:
(参数) => {语句块} // => 读作 gose to
参数中的类型可以省略
如果只有一个参数,圆括号可以省略
如果没有参数,圆括号不可以省略
语句块如果只有一行代码,花括号可以省略
示例:
MyDel del = delegate(int y) { return y += 3; }; //匿名方法 MyDel del1 = (int y) => {return y += 3;} // Lambda表达式 MyDel del2 = (y) => {return y += 3;} // 省略参数类型 MyDel del3 = y => y += 3; // 省略圆括号和花括号,虽然没有return,但仍会返回y的值
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。