第七章:委托和事件--委托(c#高级编程 第6版)
委托概述:
- .net以委托的方式实现了函数指针的概念,与C语言不同,.net的委托是类型安全的。
- 当把方法传送给其他方法时,需要使用委托。
- 关于委托的类型安全:委托封装了方法的细节,定义委托时必须给出它所代表的方法签名和返回类型等全部细节。
1. 声明委托
-
定义委托,就是告诉编译器这种类型的委托代表了那种类型的方法,然后创建该委托的一个或多个实例。编译器在后台将创建表示该委托的一个类。
-
理解委托的一种好方式是把委托当做给方法签名和返回类型指定名称,定义委托时必须给出它所代表的方法签名和返回类型等全部细节。
-
定义一个委托,基本上是定义一个新类,所以可以在定义类的任何地方定义委托,既可以在另一个类的内部定义,也可以在任何类的外部定义。
-
委托的声明例子:
delegate double DoubleOp(double x);
2. 使用委托
- 委托在语法上总是带有一个参数的构造函数,这个参数就是委托引用的方法:这个方法必须匹配最初定义委托时的签名。
- 调用委托时,必须提供委托实例的名称,后面的括号中应包含调用委托中的方法时使用的参数。
- 给委托实例提供括号与使用该实例的Invoke方法完全相同。
- 委托推断:为减少输入,也可以直接将方法的地址赋给委托实例,编译器可以把委托实例解析为特定的类型。
- 调用时只需要被调用的方法签名是正确的,并不考虑调用此方法的是什么类型,也不管这个方法静态与否。
- 委托的使用例子:
class Program
{
//委托的定义
private delegate string GetAString();
static void Main()
{
int x = 40;
// 委托的实例化
GetAString firstStringMethod = new GetAString(x.ToString);
//或者使用如下委托推断实例化
// GetAString firstStringMethod = x.ToString;
// 委托的调用
Console.WriteLine("String is " + firstStringMethod());
// 或者使用Invoke方法调用
Console.WriteLine("String is " + firstStringMethod.Invoke());
// 以上的委托调用等同于下面一句:
Console.WriteLine("String is" + x.ToString());
}
3. 使用委托的两个例子:
- 委托数组:
using System;
using System.Collections.Generic;
using System.Text;
namespace Wrox.ProCSharp.Delegates
{
class MathOperations
{
public static double MultiplyByTwo(double value)
{
return value * 2;
}
public static double Square(double value)
{
return value * value;
}
}
delegate double DoubleOp(double x);
class MainEntryPoint
{
static void Main()
{
// 定义委托数组
DoubleOp[] operations =
{
MathOperations.MultiplyByTwo,
MathOperations.Square
//new DoubleOp(MathOperations.MultiplyByTwo),
//new DoubleOp(MathOperations.Square)
};
for (int 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.414);
Console.WriteLine();
}
}
static void ProcessAndDisplayNumber(DoubleOp action, double value)
{
// 调用委托
double result = action(value);
Console.WriteLine(
"Value is {0}, result of operation is {1}", value, result);
}
}
} - 为什么要用委托?另一个例子
例如,如何写一个能给任何对象排序的冒泡排序函数Sort()?由于不知道如何具体比较两个对象的大小,这样实现起来很困难。
一个解决办法是:给某一类型的对象数组排序时,将比较这一类型两个对象大小的方法也传给Sort函数,通过委托来实现。
1. 排序函数的实现:
using System;
using System.Collections.Generic;
using System.Text;
namespace Wrox.ProCSharp.Delegates
{
// 定义委托
delegate bool Comparison(object x, object y);
class BubbleSorter
{
// 接受特定类型的比较方法做为参数
static public void Sort(object[] sortArray, Comparison comparer)
{
for (int i = 0; i < sortArray.Length; i++)
{
for (int j = i + 1; j < sortArray.Length; j++)
{
// 使用特定类型的方法进行比较
if (comparer(sortArray[j], sortArray[i]))
{
object temp = sortArray[i];
sortArray[i] = sortArray[j];
sortArray[j] = temp;
}
}
}
}
}
}
View Code3. 主函数using System;
using System.Collections.Generic;
using System.Text;
namespace Wrox.ProCSharp.Delegates
{
class Employee
{
private string name;
private decimal salary;
public Employee(string name, decimal salary)
{
this.name = name;
this.salary = salary;
}
public override string ToString()
{
return string.Format("{0}, {1:C}", name, salary);
}
// 定义该类型的比较方法
public static bool CompareSalary(object x, object y)
{
Employee e1 = (Employee)x;
Employee e2 = (Employee)y;
return (e1.salary < e2.salary);
}
}
}
View Codeusing System;
using System.Collections.Generic;
using System.Text;
namespace Wrox.ProCSharp.Delegates
{
class Program
{
static void Main()
{
Employee[] employees =
{
new Employee("Bugs Bunny", 20000),
new Employee("Elmer Fudd", 10000),
new Employee("Daffy Duck", 25000),
new Employee("Wiley Coyote", (decimal)1000000.38),
new Employee("Foghorn Leghorn", 23000),
new Employee("RoadRunner'", 50000)};
// 传递该类型的比较方法,以用于Sort函数的比较
BubbleSorter.Sort(employees, Employee.CompareSalary);
foreach (var employee in employees)
{
Console.WriteLine(employee);
}
//for (int i = 0; i < employees.Length; i++)
// Console.WriteLine(employees[i].ToString());
}
}
}
4. 多播委托
- 什么是多播委托,如何使用多播委托
- 一个委托可以包含多个方法,这种委托称之为多播委托(MulticastDelegate)
- 如果调用多播委托,就会按顺序连续调用多个方法
- 因此,委托的签名就必须返回void,否则就只能得到委托调用的最后一个方法的结果
- 多播委托可以识别+、+=、—、—=,以添加或删除方法
// 可以这样使用多播委托
DoubleOp operations = MathOperations.MultiplyByTwo;
operations += MathOperations.Square;
// 也可以这样使用多播委托
DoubleOp operation1 = MathOperations.MultiplyByTwo;
DoubleOp operation2 = MathOperations.Square;
DoubleOp MulticastDelegate = operation1 + operation2;
- 一个使用多播委托的实例
MulticastDelegateusing System;
using System.Collections.Generic;
using System.Text;
namespace MulticastDelegate
{
delegate void DoubleOp(double value);
class MathOperations
{
// 方法1
public static void MultiplyByTwo(double value)
{
double result = value * 2;
Console.WriteLine(
"Multiplying by 2: {0} gives {1}", value, result);
}
// 方法2
public static void Square(double value)
{
double result = value * value;
Console.WriteLine("Squaring: {0} gives {1}", value, result);
}
}
class Program
{
// 接收多播委托的方法
static void ProcessAndDisplayNumber(DoubleOp action, double valueToProcess)
{
Console.WriteLine("\nProcessAndDisplayNumber called with value = " +
valueToProcess);
action(valueToProcess);
}
// 主函数
static void Main()
{
// 多播委托
DoubleOp operations = MathOperations.MultiplyByTwo;
operations += MathOperations.Square;
ProcessAndDisplayNumber(operations, 2.0);
ProcessAndDisplayNumber(operations, 7.94);
ProcessAndDisplayNumber(operations, 1.414);
Console.WriteLine();
}
}
} - 手动迭代多播委托中的方法列表
- 对同一个多播委托调用方法链的顺序并未正式定义,因此应该尽量避免编写依赖于以特定顺序调用方法的代码
- 多播委托包含一个逐个调用的委托集合,如果通过委托调用的一个方法跑出了异常,整个迭代就会停止
- 为了避免这个问题,可以通过Delegate类的GetInvocationList()方法获取Delegate对象数组,手动迭代方法列表
View Codeusing System;
namespace Wrox.ProCSharp.Delegates
{
class Program
{
static void One()
{
Console.WriteLine("One");
// 抛出异常
throw new Exception("Error in one");
}
static void Two()
{
Console.WriteLine("Two");
}
public delegate void DemoDelegate();
static void Main()
{
// 多播委托
DemoDelegate d1 = One;
d1 += Two;
// 获取 DelegateList
Delegate[] delegates = d1.GetInvocationList();
// 手动迭代各个Delegate
foreach (DemoDelegate d in delegates)
{
try
{
d();
}
catch (Exception)
{
Console.WriteLine("Exception caught");
}
}
}
}
}
5. 匿名方法
- 一个例子
匿名方法例子using System;
namespace Wrox.ProCSharp.Delegates
{
class Program
{
delegate string DelegateTest(string val);
static void Main()
{
string mid = ", middle part,";
// 使用匿名方法,减少代码量
DelegateTest anonDel = delegate(string param)
{
param += mid;
param += " and this was added to the string.";
return param;
};
Console.WriteLine(anonDel("Start of string"));
}
}
}
- 使用匿名方法注意事项:
- 必须遵循两个原则
(1)在匿名方法中不能使用跳转语句跳到该匿名方法的外部
(2)反之亦然,外部跳转语句不能跳转到匿名方法的内部 - 匿名方法内部不能访问不安全的代码
- 匿名方法不能访问在该方法外部定义的ref和out参数,但可以使用在匿名方法外部定义的其他变量
- 如果需要匿名方法多次编写同一个功能,就不要使用匿名方法
- 必须遵循两个原则