委托
- 把一个函数作为参数传递
- 函数可以直接赋值给一个委托对象、委托的签名必须跟函数的签名一样
- 是C#中类型安全的,可以订阅一个或者多个具有相同签名方法的函数指针。
使用委托对的步骤
- 1.声明一个委托类型。类似方法声明,但是没有实现块。
- 2.使用该委托类型声明一个委托变量。
- 3.创建委托类型的对象,并把它赋值给委托变量。新的委托对象包括指向某个方法的引用,这个方法和第一步定义的签名和返回类型一致。
- 4.可以选择为委托方法增加其它方法。方法必须与定义的委托类型有相同的签名和返回类型。
- 5.在代码中可以像调用方法一样调用委托。在调用委托时,其包含的每一个方法都会被执行。
//声明委托对象 delegate void MyDel(int value); class Program { static void Main(string[] args) { Program program = new Program(); //声明委托变量 MyDel del; //创建随机整数生成器对象,并得到0到99之间的一个随机数 Random rand = new Random(); int randomValue = rand.Next(99); //创建一个包含PrintLow 或者PrintHigh 的委托对象,并赋值给del变量 del = randomValue < 50 ? new MyDel(program.PrintLow) : new MyDel(program.PrintHigh); del(randomValue); Console.ReadKey(); //某次输出:38-Low Value 30-Low Value 92-High Value } void PrintLow(int value) { Console.WriteLine("{0}-Low Value", value); } void PrintHigh(int value) { Console.WriteLine("{0}-High Value", value); } }
比较创建类与使用类和委托的过程
null | 类 | 委托 |
---|---|---|
声明类型 | 声明类 | 声明委托(类型) |
声明类型的变量 | 声明类类型的变量 | 声明委托类型的变量 |
填充变量 | 创建类的实例并且把它的引用赋值给变量 | 创建委托的实例并且把它的引用赋值给变量,然后增加第一个方法 |
使用变量 | 使用类对象 | 调用委托对象 |
- 委托保存的方法可以来自任何类或结构,只有委托的返回类型和签名匹配
- 方法可以是静态方法,也可以是实例方法
- 调用委托时,会执行其调用列表的所有方法
组合委托
MyDel delA = Method1; MyDel delB = Method2; //组合调用列表 MydDel delC = delA + delB;
- 委托是恒定的,委托对象被创建后不能再被改变。
委托的添加方法和移除方法
- 添加:+= 运算符
- 实际是创建了一个新的委托
- 移除:-= 运算符
- 实际是创建了一个新的委托
- 若在调用列表中的方法有多个实例,-=运算符将从列表最后开始搜索,并且移除第一个与方法匹配的实例。
- 试图删除委托中不存在的方法没有效果
- 试图调用空委托会抛出异常。
委托示例
//定义一个没有返回值和参数的委托类型 delegate void PrintFunction(); 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 pf -= Test.Print2; if (null != pf) { //调用委托 pf(); } else { Console.WriteLine("Delegate is empty!"); } Console.ReadKey(); //输出: //Print1--instance //Print2--static //Print1--instance pf -= Test.Print2; pf -= t.Print1; pf -= Test.Print2; pf -= t.Print1; if (null != pf) { //调用委托 pf(); } else { Console.WriteLine("Delegate is empty!"); } Console.ReadKey(); //输出:Delegate is empty! } } class Test { public void Print1() { Console.WriteLine("Print1--instance"); } public static void Print2() { Console.WriteLine("Print2--static"); } }
调用带返回值的委托
- 若委托有返回值并且在调用列表中有一个以上的方法,会发生以下的情况:
- 调用列表中最后一个方法返回的值就是委托调用返回的值。
- 调用列表中所有其它方法的返回值都会被忽略。
//声明有返回值的方法 delegate int MyDel(); class Program { static void Main(string[] args) { MyClass mc = new MyClass(); //创建并初始化委托 MyDel myDel = mc.Add2; //增加方法 myDel += mc.Add3; myDel += mc.Add2; Console.WriteLine("Value:{0}", myDel()); Console.ReadKey(); } //输出:Value:12 } class MyClass { int IntValue = 5; public int Add2() { IntValue += 2; return IntValue; } public int Add3() { IntValue += 3; return IntValue; } }
myDel();---> Add2();返回值7被忽略
--->Add3();返回值10被忽略--->Add2();使用返回值12
调用带引用参数的委托
- 若委托有引用参数,参数值会根据调用列表中的一个或者多个方法的返回值而改变
- 在调用委托列表中的下一个方法时,参数的新值(不是初始值)会传给下一个方法。
delegate void MyDel(ref int x); class Program { static void Main(string[] args) { MyClass mc = new MyClass(); MyDel myDel = mc.Add2; myDel += mc.Add3; myDel += mc.Add2; int x = 5; myDel(ref x); Console.WriteLine("Value:{0}", x); Console.ReadKey(); //输出:Value:12 } } class MyClass { public void Add2(ref int x) { x += 2; } public void Add3(ref int x) { x += 3; } }
myDel();---> Add2(x=5);引用x的初始值
--->Add3(x=7);引用x新的输入值--->Add2(x=10);引用x新的输入值
匿名方法
- 若方法只会被使用一次,除创建委托的语法需要,没有必要创建独立的具名方法。
- 匿名方法是在初始化委托时内联声明的方法。
//命名方法 delegate int OtherDel(int InParam); class Program { public static void Add20(int x) { return x + 20; } static void Main() { OtherDel del = Add20; Console.WriteLine("{0}",del(5)); Console.WriteLine("{0}",del(6)); Console.ReadKey(); } //输出:25 \n 26 }
//匿名方法 delegate int OtherDel(int InParam); class Program { static void Main() { OtherDel del = delegate(int x) { return x + 20; }; Console.WriteLine("{0}",del(5)); Console.WriteLine("{0}",del(6)); Console.ReadKey(); } //输出:25 \n 26 }
使用匿名方法
- 1.声明委托变量时作为初始化表达式
- 2.组合委托时在赋值语句的右边
- 3.为委托增加事件时在赋值语句的右边
匿名方法的语法
delegate + (参数列表) + 语句块
返回类型
- 匿名方法的返回类型和委托的返回类型一致
- 例如:委托的返回类型是int ,则匿名方法的实现代码也必须在代码路径中返回int
//委托类型的返回类型为int delegate int OtherDel(int InParam); static void Main() { OtherDel del = delegate(int x){ return x + 20;//返回一个int }; }
参数
- 除了参数数组,匿名方法的参数列表必须在==参数数量====参数类型及位置====修饰符==方面与委托匹配
- 在满足==委托的参数列表不包含任何out参数==和==匿名方法不使用任何参数==的情况下,可以通过使用==圆括号为空==或==省略圆括号==来简化匿名方法的参数列表。
//声明委托类型 delegate void SomeDel(int x); SomeDel SDel = delegate{//省略参数列表 PrintMessage(); Cleanup(); };
params参数
- 若委托声明的参数列表包含了params参数,则匿名方法的参数列表将忽略params关键字
delegate void SomeDel(int x,params int[] y); SomeDel SDel = delegate(int x,int[] y) { ... };
Lambda表达式
匿名方法转换为Lambda表达式的步骤
- 1.删除delegate关键字
- 2.在参数列表和匿名方法主体之间放Lamdba运算符=>。读作“goes to”
//匿名方法 MyDel del = delegate(int x) {return x + 1;}; //Lambda表达式 MyDel del = (int x)=>{return x + 1;};
- 编译器通过推断可以进一步简化Lambda表达式
//1.省略类型参数 MyDel del = (x)=>{return x+1;}; //2.只有一个隐式参数类型,省略圆括号 MyDel del = x=>{return x+1;}; //3.若语句块包含了一个返回语句,则可将语句块替换为return后的表达式 MyDel del = x=> x+1;
- 参数列表要点
- 1.必须在参数数量、类型和位置上与委托相匹配
- 2.表达式的参数列表中的参数不一定需要包含类型(隐式类型),除非委托有ref或者out参数,此时必须注明类型(显示类型)
- 3.如果只有一个参数,并且是隐式类型的,周围的圆括号可以被省略,否则必须有括号。
- 4.若没有参数,必须使用一组空的圆括号。
(参数,参数,) =>{语句}表达式 (参数) =>{语句}表达式 参数 =>{语句}表达式 () =>{语句}表达式