在最近的项目中有使用待委托,好久没用过了也有点生疏。因此打算写个博客记录一下。
1、什么是委托
学过c++语言的同学应该都很熟悉指针,那么委托就相当于c#中的指针。但是在c++中,函数指针只不过是一个指向内存位置的指针,它不是类型安全的。我们无法判断这个指针实际指向什么,像参数和类型就更无从知晓。但是.NET的委托完全不同,委托是类型安全的类,它定义了返回类型和参数类型。委托类不仅包含对方法的引用,也可以包含对多个方法的引用。
当要把一个方法传递给其他方法时,就需要用到委托。在过去我们习惯将数据作为参数传递给方法,但是现在要将方法传递给方法。
在c和c++中,只能提取函数的地址,并作为一个参数传递它。c没有类型安全。可以把任何函数传递给需要函数指针的方法。但是这种直接方法不仅会导致一些关于类型安全的问题,而且没有意识到:在进行面向对象编程时,几乎没有方法是孤立存在的,而是在调用方法之前通常需要与类实例相关联。所以在.NET在语法上不允许使用这种直接方法。如果要传递方法,就必须把方法的细节封装在一种新类型的对象中,即委托。委托只是一种特殊类型的对象,起特殊之处在于,我们之前定义的所有对象都包含数据,而委托包含的只是一个或多个方法。
a、声明委托
定义一个委托
delegate void IntMethodInvoker(int x);
在这个实例中,定义了一个委托IntMethodInvoker,并指定该委托的每个实例都可以包含一个方法的引用,该方法带有一个int参数,并返回void。理解委托的一个要点就是它们的类型安全性非常高。在定义委托时,必须给出它所表示的方法的签名和返回类型等细节。
理解委托的一种好的方式就是把委托当做这样一件事件,它给方法的签名和返回类型指定名称。
假如我们定义一个委托,该委托表示的方法有两个long类型的参数,返回类型为double。可以写成如下代码:
delegate double Longs(long first,long second);
b、使用委托
public delegate int Js(int word,int b);static void Main(string[] args){Js js = new Js(JiSuan);var result = js(2, 3);Console.WriteLine(result);}
public static int JiSuan(int word, int num){return word + num;}
在上述代码中,我们实例化了一个Js的一个委托,并对他进行初始化,使他引用了JiSuan方法。在c#中,委托在语法上总是接收一个参数的构造函数,这个参数就是委托引用的方法。这个方法必须匹配最初定义委托时的签名。所以在这个示例中如果用一个一个不带参数的方法来初始化Js变量就会产生一个编译错误。
委托的一个特征就是它们的类型是安全的,可以确保被调用的方法的签名是正确的。有趣的是,它们不关心什么累的对象上调用该方法。甚至补考虑该方法是静态方法还是实例方法。
给定委托的实例可以引用任何类型的任何对象上的实例方法或静态方法 只要方法的签名匹配委托的签名即可。
2、Action<>和Func<>委托
除了为每个参数和返回类型定义一个新的委托之外,还可以使用Action<>和Func<>委托。泛型Action<>委托表示引用一个void返回类型的方法。这个委托类型存在不同的变体,可以传递至多16中不同参数的参数类型。没有泛型参数的Action可以调用没有参数的方法。Action<int T>调用带参数的方法,Action<int T1,int T2>调用两个参数的方法
Func<T>委托可以用已类似的方式使用。Func<T>允许调用带返回类型的方法。与Action<T>类似。它也定义了不同的变体,至多可传递16个参数类型和一个返回类型。Func<out Tresult>委托类型可以调用带返回类型且无参数的方法,Func<int T,out TResult>调用一个带参数的方法。
static void Main(string[] args){try{ //参数类型为int,返回类型为int的委托var aa = Delegate1(JiSuan, 2, 12);Console.WriteLine(aa);}catch (Exception ex){var a = ex.ToString();}}public static int Delegate1(Func<int, int, int> func, int word,int num){return func(word,num);}public static int JiSuan(int word, int num){return word + num;}
上面我们简单介绍了委托,下面这个示例将说明委托的用途。我们要编写一个类BubbleSorter,它实现一个静态反复Sort(),这个方法的第一个参数是一个对象数组,把该数组按照升序重新排列。例如给他传递一个{5,4,6,1},则返回的结果应该是{1,4,5,6}。
对于上诉要求我们可以选择冒泡排序算法,它非常适合int,但我们希望Sort()方法能给任何对象排序。这样我们使用之前对于int的冒泡排序就有问题了,因为他需要比较数组中的两个对象,看看那个更大。可以对int进行这样的比较,但如何对没有实现> 运算符的类型进行比较?。答案是能识别该类的客户端代码必须在委托中传递一个封装的方法,这个方法可以进行比较。另外,对于变量我们不限定类型,使用泛型类型就可以实现泛型方法Sort()。
对于接收类型T的泛型方法Sort<T>(),需要一个比较方法,起两个参数的类型是T,if比较的返回类型是bool。这个方法可以coFunc<T1,T2,TResult>委托中引用,其中T1\T2的类型相同:Func<T,T,bool>。
给Sort()方法指定一下签名:
static public void Sort<T>(IList<T> sortArray,Func<T,T,bool> comparison)
这个方法的文档说明,comparison必须引用一个方法,该方法带有两个参数,如果第一个参数的值小于第二参数,就返回true。
设置完毕后,下面定义BubbleSorter类
class BubbleSorter
{static public void Sort<T>(IList<T> sortArray,Func<T,T,bool> comparison){bool swapped=true;for(int i =0;i<sortArray.Count-1;i++){if(comparison(sortArray[i+1],sortArray[i])){T temp = sortArray[i];sortArray[i]=sortArray[i+1];sortArray[i+1] =temp;swapped=true;} }while(swapped);}
}
为了使用这个类,需要定义另一个类,从而进行排序。在本例中,假定移动公司有一个员工列表,要根据薪水进行排序。每个员工分别由类Employee的一个实例表示,如下所示
public class Employee{public Employee(string name, decimal salary){this.Name = name;this.Salar = salary;}public string Name { get; private set; }public decimal Salar { get; private set; }public static bool CompareSalary(Employee e1, Employee e2){return e1.Salar < e2.Salar;}}
注意,为了匹配Func<T,T,bool>委托的签名,这个类中必须定义CompareSalary,他的参数是两个Employee的引用,并返回一个bool。在实现的比较代码中,根据薪水进行比较。
下面编写客户端代码,完成排序
public static void Main(string[] args){Employee[] employees = {new Employee ("bUGS", 2000 ),new Employee ("bUG1", 3000 ),new Employee ("bUG2", 2100 )};BubbleSorter.Sort(employees, Employee.CompareSalary);}