最近给项目组面人,面到一些刚从学校毕业的同学,号称熟练甚至是精通C#。一开始,每每看到简历上写着“精通C#”,都会让人有一种莫名的兴奋。然而,结果往往让人比较失望。经过几次这样的经历后,发现有些简历水分过多,大部分而的确比较熟练,但不是C#,而是C++。或者说,他们“精通”的,是学校所教授的部分,而对于C#与C++不同的部分,就显得十分生疏,甚至只是“听过而已”。
而面试时往往只要问问很简单的问题,就可以看出被面试者对C#的熟练程度了。
例如,要求应试者写一个委托,就可以看出对委托语法的理解。
好,切入正题,什么是委托?委托的实质是什么?
大家在C++学习过程中,一定会接触到各种各样的指针,其中,有一种指针是指向函数或者说是指向方法的,我们可以通过调用这个指针来调用其指向的方法。但是,这样的指针是不安全的。如果我们简单的把C++的指针认为是一个记录内存地址的空间,那么,方法指针里记录的,就是目标方法的调用地址。但是,C++并没有对指针指向的对象加以任何的限制,你不知道这个方法会返回什么,不知道这个方法要接收多少个参数,也不知道接收的参数又是什么类型,并且,在C++中,指针还可以参与运算的,因此,对于调用者而言,除了看到一个地址,其余一概不知道。一切要等到调用以后,才真向大白。这样的指针甚至常常被一些不轨之人所利用,让方法指针指向一个破坏系统的方法……呵呵,就像给你一把钥匙,让你开一个盒子。盒子里面是什么?也许是蛋糕,也许,那就是潘多拉之盒……谁知道呢。:-)
由于方法指针不完善,C#针对这一现象进行了改进——委托。委托的作用与方法指针一样,用它来指向一个方法,并且提供给客户程序调用。但是,这个方法指针是有限制的,它必规定好了所指向方法的返回值、参数个数以及各个参数的类型。因此,这把“钥匙”能开蛋糕盒却打不开潘多拉之盒……嗯,感觉安全多了。
因此,所谓委托,是指确定了目标方法签名的方法指针——如果你认为C#中指针的概念已经被消灭,那么,好吧,换个说法也行:所谓委托,是指可以调用目标方法并且确定了目标方法签名的一种特殊的对象。^o^
具体到C#中语法中,委托又可以分为委托声明和委托实例两种,而它们又同时都可以简称为委托。所以,当我们说到委托时,需要根据上下文来判断一个委托是指委托声明还是指委托实例。
委托声明,顾名思意,用来声明委托(实例)所指向的方法的签名。它以delegate关键字开头,后面跟的便是方法签名,例如,当我想要指向的方法为:
public string MergeString(string s1, string s2)
我们就可以声明一个有两个string参数并且返回一个string的委托:
delegate string TestDelegate(string s1, string s2);
委托声明本身并未指向任何方法,因此,它不可以直接被调用。但是,我们可以通过委托(声明)来实例化对象,这样的对象就被称为委托实例。委托实例的创建与用类实例化一个对象类似,但有一个约定,要把目标方法的方法名作为参数传入,例如:
public void Print(string s1, string s2)
{
TestDelegate testDelegate = new TestDelegate(MergeString);
string newString=testDelegate(s1, s2)
Console.WriteLine(newString);
}
方法Print中第一行,我们就利用一个委托声明得到了一个委托实例。第二行,调用这个委托。第三行,输出结果。就这么简单。
好,小结一下,委托的实质是安全的方法指针;委托分为委托声明和委托实例;使用委托时,先声明,然后实例化,最后调用。
那么,现在就有一个很有趣的问题出现了:我们为什么要用委托?在这个例子中,我不仅仍然要写整个方法,而且又要声明委托、又要实例化……为什么要弄得这么复杂?委托写我们带来了什么?
委托给我们带来的最大好处时:其可以通过编程方法来动态的调用别的方法。这意味着什么?这意味着,当我们把委托作为参数时,写一个代码模板,就可以让其以一定的方式执行不同的代码。举例说明:
我们有一个现有的方法,可以打印出当前的时间:
public void PrintTime()
{
Console.WriteLine(DateTime.Now.ToString("HH:MM:ss"));
}
我们有另外一个方法,可以打印出当前的日期:
public void PrintDate()
{
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd"));
}
恰巧,或者非常不巧,当前的程序里,这两个方法都需要经常被循环的调用,那么,我们就可以利用一个方法,把这种循环封装起来,以避免不停的在程序里写循环:
public static void MultipleRun(RunOnce method, int count)
{
for (int i = 0; i < count; i++)
{
method();
}
}
呵呵,接下来的事情,就是尽情的调用了,各1000次怎么样?
TestClass tc = new TestClass();
MultipleRun(tc.PrintDate, 1000);
MultipleRun(tc.PrintTime, 1000);
觉得不过瘾就自己改参数吧^o^。
最后,贴个完整的、简单到毫无意义的示例源码^_^:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication4
{
class Program
{
delegate void RunOnce();
private static void MultipleRun(RunOnce method, int count)
{
for (int i = 0; i < count; i++)
{
method();
}
}
static void Main(string[] args)
{
TestClass tc = new TestClass();
MultipleRun(tc.PrintDate, 1000);
MultipleRun(tc.PrintTime, 1000);
}
}
class TestClass
{
public void PrintTime()
{
Console.WriteLine(DateTime.Now.ToString("HH:mm:ss"));
}
public void PrintDate()
{
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd"));
}
}
}
欢迎讨论!
Little knowledge is dangerous.