目录
上节中,对委托和事件进行了较为概括的描述,本节将从各自方面单独的进行描述。
使用C等面向过程语言的朋友都知道,函数(方法)是可以作为另一个函数的参数而带入并调用,这种技术在很多领域都广泛的被用到:比如在嵌入式开发环境中的回调函数等。但面向对象语言中,函数是作为类的一个成员必须通过对象才能进行访问。那么,是不是就没有办法解决像面向过程语言中这种只调用某个函数的技术途径呢?当然有办法,本人在学习过程中大体总结了2中方法,其中之一是使用委托,其二是通过接口来实现,如果你有更好的方法,不妨说出来大家学习学习。
废话不多,言归正传
从上节中【C#委托,事件(一) http://www.cnblogs.com/Youhei/archive/2010/05/04/1727544.html 】对委托的描述可知,委托是一个类,必须用和声明委托拥有相同的签名来初始化它。下面还是以一个列子来描述上述问题的解决方法。
首先,创建一个控制台应用程序,并创建一个类,该类中的方法成员(为了简单起见,都定义为静态的)将被作为委托初始化的参数:
public class MathOper { public static double Multiply(double val) { return val * 2; } public static double Square(double val) { return val * val; } }
然后,在该命名空间内其他类外定义匹配上述类中方法的委托:
public delegate double DelegateFuction(double val);
注意:C#中定义一个委托是指定义一个新类,委托实现为派生自基类System.MulticastDelegate类,而该类又派生自System.Delegate类。C#编译器知道这个类,会使用其委托语法,这是C#语言与基类共同合作的一个示例,我们不需要了解其具体的执行请况。
接下来,就可以创建使用上述类和委托的调用类了:
public class Counter { private double myValue; public double Value { set { this.myValue = value; } } public void Deal(DelegateFuction dele) { if (dele != null) { String str = "Deal value is:" + dele(this.myValue).ToString(); Console.WriteLine(str); } } }
该类中Value接收外界传入的数据,其中方法Deal的参数是委托DelegateFuction 类型的,要求传入一个委托,有了上述的两个类,最后在Program类的Main入口函数中创建对象调用即可:
class Program { static void Main(string[] args) { Counter ct = new Counter(); ct.Value = 10; ct.Deal(new DelegateFuction(MathOper.Multiply)); //ct.Deal(MathOper.Multiply); ct.Deal(new DelegateFuction(MathOper.Square)); //ct.Deal(MathOper.Square); } }
运行,结果为:
眼明的朋友可能已经对上述用”//”注释的代码感兴趣了。其实,自C#2.0后就用委托推断扩充了委托的用法。为了减少用户输入量,只需要委托实例就可以传送地址的名称,这成为委托推断。将上述代码中注释的代码放开,可以得到和上行代码同等的效果。
还有就是,在初始化委托时,使用方法的名称,如MathOper.Multiply,而不是MathOper.Multiply(); MathOper.Multiply()是方法的执行。
首先创建一个接口:
public interface InterFaceFuction { double IFuction(double val); }
然后创建继承自该接口的类:
public class InterBase : InterFaceFuction { #region InterFaceFuction 成员 public double IFuction(double val) { return val * 2; } #endregion }
在上述类Counter中重载Deal函数:
public void Deal(InterFaceFuction fun) { if (fun != null) { String str = "Deal value is:" + fun.IFuction(this.myValue).ToString(); Console.WriteLine(str); } }
在Progra类的Main方法中添加:
ct.Deal(new InterBase());
示例代码
在上述示例中,我们在定义委托实例时都通过已经存在的方法去初始化委托变量,这就要求首先方法必须已经存在,但如果我们需要初始化委托变量的方法不存在,或在初始化委托实例时并不需要对立存在的方法时,就可以使用匿名方法去初始化委托实例。
使用匿名方法初始化委托实例时,需要用delegate关键字修饰,如下:
委托类型 委托变量 = delegate(参数类型 参数变量)
{
[匿名方法逻辑处理...]
};
前面的示例中,在Main方法内通过MathOper.Multiply()方法初始化委托变量df,我们也可以这样来初始化该委托变量:
class Program { static void Main(string[] args) { Counter ct = new Counter(); DelegateFuction df = delegate(double val) { return val * 2; }; //DelegateFuction df = MathOper.Multiply; ... } }
匿名方法的却是减少了代码编写量,可以在匿名方法内应用外部部分变量,但我们在使用匿名方法时必须注意一下两点:
自C#3.0后,我们还可以进一步简化匿名方法的编写,就是接下来我们要讲述的λ表达式。
使用λ表达式时,定义委托类型和前面一样,只是在初始化委托实例时稍有不同,还是先看示例,将“匿名方法”一节中的Main稍稍变动下:
class Program { static void Main(string[] args) { Counter ct = new Counter(); //DelegateFuction df = delegate(double val) //{ // return val * 2; //}; DelegateFuction df = val => { return val * 2; }; ... } }
上述代码中val后面的=>符号是λ运算符,val是传入的参数,如果是多个参数,可以用括号()括起来。当然,也是可以定义参数的
类型,但并不是必须的,因为编译器知道每一个参数的类型。
上节中,使用的每个委托都只包含一个方法调用,即调用委托的次数与调用方法的次数相等。如果使用一个委托调用不同的多个方法时这就是多播委托。多播委托可以包含多个不同的方法。如果调用多播委托就可以按顺序连续调用多个方法。
将上节中示例进行改造,结果如下(仅改造Program类中Main函数的处理逻辑):
class Program { static void Main(string[] args) { Counter ct = new Counter(); DelegateFuction df = MathOper.Multiply; df += MathOper.Square; ct.Value = 10; ct.Deal(df); //ct.Deal(new DelegateFuction(MathOper.Multiply)); //ct.Deal(MathOper.Multiply); //ct.Deal(new DelegateFuction(MathOper.Square)); //ct.Deal(MathOper.Square); //ct.Deal(new InterBase()); } }
另外,值得注意的是:“如果使用多播委托,就应注意对同一个委托调用方法链的顺序并未正式定义,因此应避免编写依赖于以特定顺序调用方法的代码”。并且,多播委托包含一个逐个调用的委托集合,如果通过委托调用的一个方法抛出异常,整个调用迭代就会停止。
运行,结果如下图:
很奇怪!为什么只调用了MathOper.Square()方法呢?其实,在Main方法中的多播委托DelegateFuction 的实例df的方法链执行时MathOper.Multiply()方法和MathOper.Square()方法都被调用,只是多播委托只返回方法链中最后一个方法的返回值,所以输出的结果就只有MathOper.Square()方法返回的数据了,这里,我们必须注意,使用多播委托时,需要将委托的返回类型标示为void类型,否则只返回方法链中最后一个方法的返回值。