本章内容:
8.1.3 简单的委托示例
首先定义一个类MathOperations,它有两个静态方法,对double类型的值执行两个操作。
public class MathOperations { public static double MultiplyByTwo(double value) { return value*2; } public static double Square(double value) { return value*value; } }
下面使用自定义的委托调用这两个方法
internal delegate double DoubleOp(double x);
private static void Main() { DoubleOp[] operations = { MathOperations.MultiplyByTwo, MathOperations.Square }; for (var i = 0; i < operations.Length; i++) { Console.WriteLine("Using operations[{0}]", i); ProcessAndDisplayNumber(operations[i], 2.0); ProcessAndDisplayNumber(operations[i], 7.94); ProcessAndDisplayNumber(operations[i], 1.141); Console.WriteLine(); } Console.ReadLine();
} private static void ProcessAndDisplayNumber(DoubleOp action, double value) { var result = action(value); Console.WriteLine("Value is {0},result of operation is {1}", value, result); }
运行这个示例,得到如下所示的代码:
8.1.4 Action<T>和Func<T>委托
除了自定义的委托类型外,我们还可以使用Action<T>委托和Func<T>委托。
泛型Action<T>委托表示引用一个void返回类型的方法,因为这个委托类存在不同的变体,所以可以传递至多16种不同的参数类型,
没有泛型参数的Action类可调用没有参数的方法。
Func<T>允许调用带返回类型的方法,与Action<T>类似,Func<T>也定义了不同的变体,至多也可以传递16个参数类型和一个返回类型。
Func<out TResult>委托类型可以调用带返回类型且无参数的方法。
理解了这两个委托后,我们就可以将上一节种定义的DoubleOp委托删除,改为Func<T>委托,如下所示:
Func<double,double> [] operations = { MathOperations.MultiplyByTwo, MathOperations.Square };
然后修改ProcessAndDisplayNumber方法的第一个参数类型,如下所示:
private static void ProcessAndDisplayNumber(Func<double,double> action, double value) { var result = action(value); Console.WriteLine("Value is {0},result of operation is {1}", value, result); }
8.1.5 BubbleSorter示例
现在有一个类BubbleSorter,它有一个静态方法Sort(),这个方法只有一个int数组类型的参数,用来把数组按照升序重新排列。
假如传递给它的是int数组:{0,5,6,2,1},则方法执行完毕后int数组为:{0,1,2,5,6}。
这就是经典的冒泡排序,是一种简单的排序方法。它适合于一小组数字。下面是代码:
public class BubbleSorter { public static void Sort(int[] array) { bool swapped; do { swapped = false; for (var i = 0; i < array.Length - 1; i++) { if (array[i] <= array[i + 1]) { continue; } var temp = array[i]; array[i] = array[i + 1]; array[i + 1] = temp; swapped = true; } } while (swapped); } }
它非常适合于int,但是我们希望Sort()方法能给任何对象排序,这时上面的比较方法就有问题了,为了实现这个功能,我们要使用泛型方法和
委托。如下所示:
public class BubbleSorter { public static void Sort<T>(IList<T> sortArray, Func<T, T, bool> comparison) { bool swapped; do { swapped = false; for (var i = 0; i < sortArray.Count - 1; i++) { if (!comparison(sortArray[i + 1], sortArray[i])) { continue; } T temp = sortArray[i]; sortArray[i] = sortArray[i + 1]; sortArray[i + 1] = temp; swapped = true; } } while (swapped); } }
为了使用这个类,我们需要定义一个员工类,根据他们的薪水进行排序。
public class Employee { public string Name { get; private set; } public decimal Salary { get; private set; } public Employee(string name, decimal salary) { Name = name; Salary = salary; } public override string ToString() { return string.Format("{0},{1:C}", Name, Salary); } public static bool CompareSalary(Employee e1, Employee e2) { return e1.Salary < e2.Salary; }
}
下面编写客户端代码,完成排序功能:
private static void Main() { Employee[] employees = { new Employee("Jerry", 20000), new Employee("John", 10000), new Employee("Tom", 25000), new Employee("Lucy", 1000000.38M), new Employee("Lili", 23000) }; BubbleSorter.Sort(employees, Employee.CompareSalary); foreach (var employee in employees) { Console.WriteLine(employee); } Console.ReadLine(); }
运行这段代码,正确显示按照薪水排列的Employee,如下图所示:
8.1.6 多播委托
前面使用的每个委托都只包含一个方法调用,委托也可以包含多个方法,这种委托称为多播委托。如果调用多播委托,
就可以按顺序连续调用多个方法。 多播委托可以识别运算符"+","+=","-"和"-="。
为了说明多播委托的用法,我们修改之前示例中的部分代码,如下所示:
public class MathOperations { public static void MultiplyByTwo(double value) { var result = value*2; Console.WriteLine("{0} Multiply by 2 equlas {1}", value, result); } public static void Square(double value) { var result = value*value; Console.WriteLine("{0} squaring equlas {1}", value, result); } }
private static void Main() { Action<double> operations = MathOperations.MultiplyByTwo; operations += MathOperations.Square; ProcessAndDispalyNumber(operations, 2.0); ProcessAndDispalyNumber(operations, 7.94); ProcessAndDispalyNumber(operations, 1.414); Console.ReadLine(); } public static void ProcessAndDispalyNumber(Action<double> action, double value) { Console.WriteLine(); Console.WriteLine("ProcessAndDispalyNumber called with value ={0}", value); action(value); }
运行代码,得到如下图所示的代码:
使用多播委托应注意,如果通过委托调用的其中一个方法抛出一个异常,整个迭代就会停止,如下面的示例所示:
private static void Main() { Action d1 = One; d1 += Two; try { d1(); } catch (Exception) { Console.WriteLine("Exception caught"); } // 运行结果 // One // Exception caught Console.ReadLine(); } private static void One() { Console.WriteLine("One"); throw new Exception("Error in one"); } private static void Two() { Console.WriteLine("Two"); }
从运行结果来看,我们看出委托只调用了第一个方法,因为第一个方法抛出了一个异常,所以委托的迭代会停止,不再调用Two()方法。
在这种情况下,为了避免这个问题,应自己迭代方法列表,为此我们修改代码如下:
private static void Main() { Action d1 = One; d1 += Two; Delegate[] delegates = d1.GetInvocationList(); foreach (Action d in delegates) { try { d(); } catch (Exception) { Console.WriteLine("Exception caught"); } } // 运行结果 // One // Exception caught // Two Console.ReadLine(); }
从运行结果我们可以看到,在捕获了异常后,将继续迭代下一个方法。
8.1.7 匿名方法
到目前为止,要想使委托工作,方法必须已经存在(即委托是用它将调用的方法的相同签名定义的)。
但还有另外一种使用委托的方式:即通过匿名方法。
下面通过一个简单的例子说明如何使用匿名方法:
private static void Main() { var str = ",Middle part,"; Func<string, string> anonDel = delegate(string param) { param += str; param += "End part"; return param; }; Console.WriteLine(anonDel("Strat Part")); // 输出结果 // Strat Part,Middle part,End part Console.ReadLine(); }
匿名方法的优点:减少了要编写的代码,不必定义仅由委托使用的方法,有助于降低代码的复杂性。
从C#3.0开始,可以使用Lambda表达式替代匿名方法。
8.2 Lambda 表达式
自C#3.0开始,可以使用Lambda表达式把实现代码赋予委托,只要有委托参数类型的地方,就可以使用Lambda表达式。
下面将前面使用匿名方法的例子改为使用Lambda表达式:
private static void Main() { var str = ",Middle part,"; Func<string, string> lambda = param => { param += str; param += "End part"; return param; }; Console.WriteLine(lambda("Strat Part")); // 输出结果 // Strat Part,Middle part,End part Console.ReadLine(); }
从上面的代码可以看出,Lambda运算符"=>"的左边列出了需要的参数,右边定义了赋予lambda变量的方法的实现代码。
8.2.1 参数
Lambda表达式有几种定义参数的方式,如果只有一个参数,只写出参数名就够了,如下所示:
Func<string, string> oneParam = s => string.Format("change uppercase {0}",s.ToUpper()); Console.WriteLine(oneParam("test")); // 输出结果 // change uppercase TEST
如果委托使用多个参数,就把参数放在花括号中。如下所示:
Func<double, double, double> twoParams = (x, y) => x*y; Console.WriteLine(twoParams(3, 2)); // 输出结果 // 6
也可以在花括号中给变量名添加参数类型:
Func<double, double, double> twoParams = (double x, double y) => x*y;
8.2.2 多行代码
如果Lambda表达式只有一条语句,在方法块内就不需要花括号和return语句,因为编译器会添加一条隐式的return语句。
Func<double, double> square = x => x*x;
添加花括号、return语句和分号是完全合法的,通常这比不添加这些符号更容易阅读:
Func<double, double> square = x => { return x*x; };
如果在Lambda表达式的实现代码中需要多条语句,就必须添加花括号和return语句:
var str = ",Middle part,"; Func<string, string> lambda = param => { param += str; param += "End part"; return param; };