1.3 C#委托机制
1.3.1 委托的定义
一个简单的例子:
张三看到餐桌上有一个桔子,由于自己忙,走不开,立刻就对着他妈妈喊:“我要吃桔子,妈妈帮我拿过来。”,接着,他妈妈听到乖儿子要吃桔子,就立刻送去给儿子了。
从某种意义上来说,把儿子发出消息要桔子的动作与妈妈送桔子给儿子的动作相关联的过程就称为委托,也就是说儿子发出消息要桔子这个事件委派给妈妈,根据他的消息内容去完成他想要做的事。
委托是C#中非常重要的一个概念,并在C#中得到了丰富的应用,如事件、线程等。委托,顾名思义,就是中间代理人的意思。具体来说,委托是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值。C#中的委托允许用户将一个对象中的方法传递给另一个能调用该方法的类的某个对象。可以将类A中的一个方法m(被包含在某个委托中)传递给一个类B,这样类B就能调用类A中的方法m。同时,还可以以静态(static)的方式或是实例(instance)的方式来传递该方法。所以这个概念和C++中的以函数指针为参数形式调用其他类中的方法的概念是十分类似的。
委托的最大的特点是,它不知道或不关心自己引用的对象的类。任何对象中的方法都可以通过委托来动态地调用,只是方法的参数类型和返回类型必须与委托的参数类型和返回类型相匹配。C#中的委托是通过继承System.Delegate中的一个类来实现的,下面是具体的步骤:
(1)声明一个委托类型,其参数形式一定要和想要包含的方法的参数形式一致。
声明语法:访问修饰符delegate数据类型的委托名(参数序列)。
定义委托基本上是定义一个新类,所以可以在定义类的任何地方来定义委托,既可以在另一个类的内部定义,也可以在任何类的外部定义,还可以在命名空间中把委托定义为顶层对象。根据定义的可见性,可以在委托定义上添加一般的访问修饰符:public、private、protected等。
(2)定义所有要定义的方法,其参数形式和第一步中声明的委托对象的参数形式必须相同。
(3)创建委托对象,并将所希望的方法包含在该委托对象中。
(4)通过委托对象调用包含在其中的各个方法。
【例1.8】使用委托的简单例子。
显示了如何运用以上的4个步骤来实现委托机制,新建一个DelegateEx108的Console控制台应用程序,代码如下所示。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication3 { class Program { //步骤1: 声明一个委托 public delegate void MyDelegate(string input); //步骤2:定义各个方法,其参数形式和步骤1中声明的委托对的必须相同 class MyClass1 { public void delegateMethod1(string input) { Console.WriteLine("This is delegateMethod1 and the input to the method is {0}", input); } public void delegateMethod2(string input) { Console.WriteLine("This is delegateMethod2 and the input to the method is {0}", input); } } //步骤3:创建一个委托对象,并将上面的方法包含其中 class MyClass2 { public MyDelegate createDelegate() { MyClass1 c2=new MyClass1(); MyDelegate d1=new MyDelegate(c2.delegateMethod1); MyDelegate d2=new MyDelegate(c2.delegateMethod2); MyDelegate d3=d1 + d2; return d3; } } //步骤4:通过委托对象调用包含在其中的方法
class MyClass3 { public void callDelegate(MyDelegate d, string input) { d(input); } } static void Main(string[] args) { MyClass2 c2=new MyClass2(); MyDelegate d=c2.createDelegate(); MyClass3 c3=new MyClass3(); c3.callDelegate(d, "Calling the delegate"); Console.ReadLine(); } } }
程序结果如图1-11所示。
图1-11 例1.8程序运行结果
1.3.2 委托的使用
1.将委托作为方法的参数
【例1.9】将方法作为方法的参数的例子。
新建一个DelegateEx109的Console控制台应用程序,代码如下所示。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DelegateEx109 { class Program { //定义委托,它定义了可以代表的方法的类型 public delegate void GreetingDelegate(string name); private static void EnglishGreeting(string name) { Console.WriteLine("Morning, " + name); } private static void ChineseGreeting(string name) { Console.WriteLine("早上好, " + name); } //注意此方法,它接受一个GreetingDelegate类型的方法作为参数 private static void GreetPeople(string name, GreetingDelegate MakeGreeting) { MakeGreeting(name); } static void Main(string[] args) { GreetPeople("Mr Zhang", EnglishGreeting); GreetPeople("张先生", ChineseGreeting); Console.ReadKey(); } } }
程序结果如图1-12所示。
图1-12 例1.9程序的运行结果
总结:委托是一个类,它定义了方法的类型,使得可以将方法当做另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else (Switch)语句,同时使程序具有更好的可扩展性。
2.将方法绑定到委托
委托类似于数据类型,那么也可以这么使用委托,代码如下所示。
static void Main(string[] args) { GreetingDelegate delegate1, delegate2; delegate1=EnglishGreeting; delegate2=ChineseGreeting; GreetPeople("Mr Zhang", delegate1); GreetPeople("张先生", delegate2); Console.ReadKey(); }
委托不同于数据类型的一个特性:可以将多个方法赋给同一个委托(将多个方法绑定到同一个委托),当调用这个委托时,将依次调用其所绑定的方法。在这个例子中,语法如下所示。
static void Main(string[] args) { GreetingDelegate delegate1; delegate1=EnglishGreeting; //先给委托类型的变量赋值 delegate1 += ChineseGreeting; //给此委托变量再绑定一个方法 //将先后调用EnglishGreeting 与ChineseGreeting方法 GreetPeople("Mr Zhang", delegate1); Console.ReadKey(); }
输出为:
Morning,Mr Zhang 早上好,Mr Zhang
实际上,还可以绕过GreetPeople方法,通过委托来直接调用EnglishGreeting和Chinese Greeting,代码如下所示。
static void Main(string[] args) { GreetingDelegate delegate1; delegate1=EnglishGreeting; //先给委托类型的变量赋值 delegate1 += ChineseGreeting; //给此委托变量再绑定一个方法 //将先后调用EnglishGreeting 与ChineseGreeting方法 delegate1("Mr Zhang"); Console.ReadKey(); }
注意,第一次用的“=”,是赋值的语法;第二次用的是“+=”,是绑定的语法。如果第一次就使用“+=”,将出现“使用了未赋值的局部变量”的编译错误。
也可以使用下面的代码来简化这一过程:
GreetingDelegate delegate1=new GreetingDelegate(EnglishGreeting); delegate1 += ChineseGreeting; //给此委托变量再绑定一个方法
既然给委托可以绑定一个方法,那么也应该有办法取消对方法的绑定,很容易想到,这个语法是“-=”,代码如下所示。
static void Main(string[] args) { GreetingDelegate delegate1=new GreetingDelegate (EnglishGreeting); delegate1 += ChineseGreeting; //给此委托变量再绑定一个方法 //将先后调用EnglishGreeting 与ChineseGreeting方法 GreetPeople("Mr Zhang", delegate1); Console.WriteLine(); delegate1 -= EnglishGreeting; //取消对EnglishGreeting方法的绑定 //将仅调用ChineseGreeting GreetPeople("张先生", delegate1); Console.ReadKey(); }
输出为:
Morning,Mr Zhang 早上好,Mr Zhang 早上好,张先生
总结:使用委托可以将多个方法绑定到同一个委托变量,当调用此变量时(这里用“调用”这个词,是因为此变量代表一个方法),可以依次调用所有绑定的方法。