委托是C#重要特性之一,C#中很多特性都是建立在委托的基础之上。
什么是委托
委托也叫代理delegate
,就是把事情交给别人去办理,例如委托律师代理打官司,委托同学代买火车票等。
法庭上律师为当事人进行辩护,律师真正执行的是当事人的陈词,律师就是一个委托对象,当事人则委托律师进行辩护。
C#中如果将一个方法委托给一个对象,这个对象就可以全权代理这个方法的执行。
函数指针
C#的委托可理解为函数的一个包装,使得C#中的函数可以作为参数进行传递,作用上相当于C/C++中的函数指针。如果函数指针想要指向某函数,函数指针的返回值类型和指向的函数的返回值类型必须相同,并且参数相同。
委托delegate
是函数指针的升级版,函数指针是C/C++语言中特有的功能。
$ vim delegate.c
#include <stdio.h>
#include <stdlib.h>
//定义函数指针类型
typedef int(*Calc)(int x, int y);
int add(int x, int y)
{
return x+y;
}
int sub(int x, int y){
return x-y;
}
int main()
{
int x = 100;
int y = 200;
int z = 0;
//函数调用,直接调用。
z = add(x,y);
printf("%d + %d = %d\n", x, y, z);
z = sub(x,y);
printf("%d - %d = %d\n", x, y, z);
//定义函数指针,间接调用。
Calc fnp1 = &add;
Calc fnp2 = ⊂
z = fnp1(x, y);
printf("%d + %d = %d\n", x, y, z);
z = fnp2(x, y);
printf("%d + %d = %d\n", x, y, z);
system("pause");
return 0;
}
$ gcc delegate.c -o delegate.exe
$ delegate.exe
100 + 200 = 300
100 - 200 = -100
100 + 200 = 300
100 + 200 = -100
定义委托
使用委托首先要定义委托,声明委托能代理什么类型的方法,类似房产中介能代理抵押贷款业务,而不能代理打官司一样。
委托的定义和方法的定义类似,只是在定义前多了一个delegate
关键字。
定义委托的语法
<访问修饰符> delegate 返回类型 委托名称();
示例
public delegate void DelagateName(int intParam, string strParam)
委托能包装的方法有一定限制
委托类型DelegateName
包装的方法需满足条件
- 方法的返回值类型必须为
void
- 方法必须有两个参数,第一个参数必须为
int
类型,第二个参数必须为string
类型。
声明委托
如果要把方法当做参数来传递的话,就要用到委托。简单来说,委托就是一个类型,这个类型可以赋值一个方法的引用。
C#中使用类分为两个阶段,首先定义类,即告诉编译器这个类由什么字段和方法组成,然后使用类实例化后的对象。在使用委托时,也需要经历两个阶段,首先定义委托,即告知编译器这个委托可以指向那些类型的方法,然后创建该委托的实例。
使用delegate
定义委托,定义委托需定义所要指向方法的参数和返回值。
//定义叫IntMethodInvoker的委托,指向带有int类型参数的方法且返回值为void。
delegate void IntMethodInvoker(int x);
delegate double TwoLongOp(long first, long second);
delegate string GetString();
使用委托
using System;
namespace CSharp
{
class Program
{
//声明委托:定义委托类型,委托类型名称为GetString。
private delegate string GetString();
//入口主函数
static void Main(string[] args)
{
/*使用委托调用ToString方法*/
int x = 100;
//声明委托:使用委托类型创建实例
//GetString method 使用委托声明一个类型叫做method
//new GetString(x.ToString) 使用new对其进行初始化,使其引用到x中的ToString()上,这样method就相当于x.ToString。
//ToString方法用来把数据转换成字符串
//GetString method = new GetString(x.ToString);
//初始化委托:直接将方法赋值为委托类型的变量
GetString method = x.ToString;
//通过委托实例调用方法,通过Invoke方法调用method所引用的方法。
//string result = method.Invoke();
//通过委托实例调用方法 简写
//通过method()执行方法就相当于x.ToString()
string result = method();
Console.WriteLine(result);//100
Console.ReadKey();
}
}
}
将委托类型作为方法的参数使用
using System;
namespace CSharp
{
class Program
{
/*定义委托*/
private delegate void PrintString();
/*使用委托作为方法的参数*/
static void PrintMethod(PrintString print)
{
print();
}
//静态方法
static void Method1()
{
Console.WriteLine("Method1");
}
//静态方法
static void Method2()
{
Console.WriteLine("Method2");
}
//入口主函数
static void Main(string[] args)
{
//定义委托变量并初始化
PrintString method = Method1;
//将委托变量作为方法参数
PrintMethod(method); //Method1
method = Method2;
PrintMethod(method); //Method2
Console.ReadKey();
}
}
}
综合案例
using System;
namespace Test
{
class Operate
{
/*数组按降序排序*/
public static bool ArraySort(int[] arr)
{
for(int i=arr.GetUpperBound(0); i>=0; i--)
{
for(int j=0; j<i; j++)
{
if(arr[j] <= arr[i])
{
Swap(ref arr[j], ref arr[i]);
}
}
}
return true;
}
/*交换两个变量的值*/
public static void Swap(ref int x, ref int y)
{
int tmp = x;
x = y;
y = tmp;
}
}
class Program
{
/*定义委托*/
public delegate bool DelegateSort(int[] arr);
static void Main(string[] args)
{
int[] arr = new int[] { 3, 2, 9, 2, 4, 1 };
Console.WriteLine("排序前");
foreach (int i in arr)
{
Console.WriteLine(i);
}
//声明委托变量
DelegateSort delegateSort;
//实例化委托,委托Operate的ArraySort排序
delegateSort = new DelegateSort(Operate.ArraySort);
//传递参数,调用委托进行排序
delegateSort(arr);
Console.WriteLine("排序后");
foreach (int i in arr)
{
Console.WriteLine(i);
}
Console.ReadKey();
}
}
}
一切皆地址,程序的本质是数据+算法。
- 变量(数据)是以某个地址为起点的一段内存中所存储的值
- 函数(算法)是以某个地址为起点的一段内存中所存储的一组机器语言指令
函数的直接调用和间接调用
- 直接调用
通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行并返回。 - 间接调用
通过函数指针来调用函数,CPU通过读取函数指针存储的值来获取函数所在地址并开始执行并返回。
Java中没有与委托相对应的功能实体
预定义委托类型
- Action 委托
- Func 委托
除了自定义的委托之外,系统给我们提供了内置的委托类型Action
和Func
。
Action委托是系统内置预定义的,无返回值的泛型类型。其方法可通泛型来实现。
using System;
namespace CSharp
{
class Program
{
//无参数且无返回值的方法
static void Print()
{
Console.WriteLine("hello");
}
//入口主函数
static void Main(string[] args)
{
//Action是系统预定义的一个委托类型
//Action指向无参数且无返回值的方法
Action action = Print;
action();
Console.ReadKey();
}
}
}
Action
委托引用了一个返回值为void
的方法,其中T
表示方法参数。
Action
Action<in T>
Action<in T1, in T2>
Action<in T1, in T2... in T6>
using System;
namespace CSharp
{
class Program
{
//无参数且无返回值的方法
static void Print()
{
Console.WriteLine("hello");
}
//有参数无返回值的方法
static void Print(string message)
{
Console.WriteLine(message);
}
static void Print(int x, int y)
{
Console.WriteLine(x+y);
}
//入口主函数
static void Main(string[] args)
{
//Action是系统预定义的一个委托类型,默认指向无参数且无返回值的方法。
Action action = Print;
action();//hello
//泛型委托:定义委托类型,此类型可以指向一个没有返回值,但带有一个参数的方法。
Action<string> act = Print;
act("world");//world
//Action可通过泛型T去指定 Action指向的方法的多个参数的类型,参数类型跟action后面声明的委托类型需保持一致。
Action<int, int> a = Print;
a(100, 200);
Console.ReadKey();
}
}
}
Func委托引用了一个带有返回值的方法,可传递0或多达16个参数类型,和一个返回类型。
Func<out TResult>
Func<in T, out TResult>
Func<in T1, in T2... in T16, out TResult>
using System;
namespace Test
{
class Calculator
{
public void Report()
{
Console.WriteLine("I have 3 methods");
}
public int Add(int x, int y)
{
return x + y;
}
public int Sub(int x, int y)
{
return x - y;
}
}
class Program
{
static void Main(string[] args)
{
Calculator calculator = new Calculator();
//直接调用
calculator.Report();
//Action委托类型
Action action = new Action(calculator.Report);
//间接调用
action.Invoke();
//简写
action();
//Function泛型委托
Func<int, int, int> fn1 = new Func<int, int, int>(calculator.Add);
Func<int, int, int> fn2 = new Func<int, int, int>(calculator.Sub);
int x = 100;
int y = 200;
int z = 0;
//z = fn1.Invoke(x, y);
z = fn1(x, y);
Console.WriteLine("{0} + {1} = {2}", x, y, z);
//z = fn2.Invoke(x, y);
z = fn2(x, y);
Console.WriteLine("{0} - {1} = {2}", x, y, z);
Console.ReadKey();
}
}
}
委托的声明(自定义委托)
- 委托是一种类
class
,类是数据类型,所以委托也是一种数据类型。
Type t = typeof(Action);
Console.WriteLine(t.IsClass);//true
- 委托的声明方式与一般的类不同,主要是为了照顾可读性和C/C++传统。
- 注意声明委托的位置,避免写错地方,结果声明成嵌套类型。
- 委托与所封装的方法必须“类型兼容”
using System;
namespace Test
{
//自定义类
class Calculator
{
public double Add(double x, double y)
{
return x + y;
}
public double Sub(double x, double y)
{
return x - y;
}
public double Mul(double x, double y)
{
return x * y;
}
public double Div(double x, double y)
{
return x / y;
}
}
//自定义委托
public delegate double Calc(double x, double y);
class Program
{
static void Main(string[] args)
{
Calculator calculator = new Calculator();
//使用委托间接调用
Calc calc = new Calc(calculator.Add);
double x = 100.00;
double y = 200.00;
double z = 0.00;
//z = calc.Invoke(x, y);
//简写
z = calc(x, y);
Console.WriteLine(z);
Console.ReadKey();
}
}
}
委托的使用
将方法当作参数传递给另一个方法
- 模板方法 借用指定的外部方法来产生结果
相当于填空题,常位于代码中部,委托有返回值。
using System;
namespace Test
{
//产品
class Product
{
public string Name { get; set; }
}
//包装
class Box
{
public Product Product { get; set; }
}
//包装工厂
class WrapFactory
{
//使用Func委托 模板方法
public Box WrapProduct(Func<Product> getProduct)
{
Box box = new Box();
Product product = getProduct.Invoke();
box.Product = product;
return box;
}
}
//生产工厂
class ProductFactory
{
public Product Make()
{
Product product = new Product();
product.Name = "Pizza";
return product;
}
public Product Build()
{
Product product = new Product();
product.Name = "Car";
return product;
}
}
class Program
{
static void Main(string[] args)
{
ProductFactory productFactory = new ProductFactory();
WrapFactory wrapFactory = new WrapFactory();
//使用模板方法
Func<Product> fn1 = new Func<Product>(productFactory.Make);
Func<Product> fn2 = new Func<Product>(productFactory.Build);
Box box1 = wrapFactory.WrapProduct(fn1);
Box box2 = wrapFactory.WrapProduct(fn2);
Console.WriteLine(box1.Product.Name);
Console.WriteLine(box2.Product.Name);
Console.ReadKey();
}
}
}
- 回调方法
callback
调用指定的外部方法
相当于流水线,常位于代码末尾,委托无返回值。
注意:委托难精通、易使用且功能强大,一旦被滥用后果非常严重。
- 委托造成方法级别的紧耦合,现实工作中要慎之又慎。
- 委托造成代码可读性下降,debug难度增加。
- 将委托回调、异步调用、多线程纠缠在一起,使得代码难以阅读和维护。
- 委托使用不当有可能造成内存泄漏和程序性能下降。