1.1 引用方法
委托是寻址方法的 .NET 版本。委托是类型安全的类。它定义了返回类型和参数的类型。委托类不仅包含对方法的引用,也可以包含对多个方法的引用。
Lambda 表达式与委托直接相关。当参数是委托类型时,就可以使用Lambda表达式实现委托引用的方法。
1.2委托
当要把 方法 传送给其他方法时,需要使用 委托。以下两个示例:
- 启动线程和任务——在C# 中,可以告诉计算机并行运行某些新的执行序列同时运行当前的任务。这种序列就是称为线程,在其中一个基类 System.Threading.Thread 的一个实例上使用方法 Start(),就可以启动一个线程。如果要告诉俺计算机启动一个新的执行序列,就必须说明要在哪里启动该序列。必须为计算机提供开始启动的方法的细节,即Thread类的构造函数必须带有一个参数,该参数定义了线程调用的方法。
- 事件——一般是通知代码发生了什么事件。GUI 编程主要处理事件,在引发事件时,运行库需要知道应执行哪个方法。这就需要把处理事件的方法作为一个参数传递给委托的。
委托只是一种特殊类型的对象,其特殊之处在于,我们以前定义的所有对象都包含数据,而委托包含的只是一个或多个方法的地址。
1.2.1 声明委托
在C#中使用一个类时,分两个阶段。首先,需要定义这个类,即告诉编译器这个类由什么字段和方法组成。然后(除非只使用静态方法),实例化类的一个对象。使用委托时,也需要经过这两个步骤。首先必须定义要使用的委托,对于委托,定义它就是告诉编译器这种类型的委托表示哪种类型的方法,然后,必须创建该委托的一个或多个实例。编译器在后台将创建表示该委托的一个类。
定义委托的语法如下:
delegate void IntMethodInvoker(int x);
上面示例中,定义了一个委托IntMethodInvoker,并制定该委托的每个实例都可以包含一个方法的引用,该方法带有一个int 参数,并返回void。理解委托的一个要点是它们的类型安全性非常高。在定义委托时,必须给出它所表示的方法的 签名和返回类型 等全部细节。
例:delegate double TwoLongsOp(long first,long second);
或 delegate string GetAString();
委托的语法类似于方法的定义,但没有方法体,定义的前面要加上关键字 delegate。因为定义委托基本上定义一个新类,所以可以在定义类的任何相同地方定义委托,也就是说,可以在另一个类的内部定义,也可以在任何类的外部定义,还可以在名称空间中把委托定义为 顶层对象。根据定义的可见性,和委托的作用域。可以在委托的定义上应用任意常见的访问修饰符。public、 private、 protected等。
委托实现为派生自基类System.MulticastDelegate 的类,System.MulticastDelegate 又派生自基类System.Delegate。
在创建委托时,所创建的委托的实例仍称为委托。
1.2.2 使用委托
下面的例子来说明使用委托:
private delegate string GetAString();
static void Main()
{
int x=40;
GetAString firstStringMethod=new GetAString(x.ToString());
Console.WriteLine("String is {0}",firstStringMethod());
}
或
using System;
namespace DelegateTest
{
class Program
{
private delegate string GetAString();
static void Main(string[] args)
{
int x = 40;
GetAString fisrtMethod = new GetAString(new Program().SayHi);
Console.WriteLine("String is {0}",fisrtMethod());
//与 Console.WriteLine("String is {0}",firstMethod.Invoke());
}
private string SayHi()//必须匹配定义委托时的签名。
{
return "Say hi!";
}
}
}
//结果为:String is Say hi!
在上面的代码中,实例化了类型为GetAString 的一个委托,并对它进行初始化,使它引用整形变量 x 的ToString() 方法。在C#中,委托在语法上总是接受一个参数的构造函数,这个参数就是委托引用的方法。这个方法必须匹配最初定义委托时的签名。所以在这个示例中,如果用不带参数并返回一个字符串的方法来初始化 firstStringMethod 变量,就会产生一个编译错误。
实际上,给委托实例提供圆括号与调用委托类的 Invoke() 方法完全相同。因为 firstStringMethod 是委托类型的一个变量,所以C# 编译器会用 firstStringMethod.Invoke() 代替 firstString()。
firstStringMethod()----->firstStringMethod.Invoke();
委托推断:只需要委托实例,就可以只传送地址的名称。委托推断可以在需要委托实例的任何地方使用。委托推断也可以用于事件,因为事件基于委托。
☆:调用上述方法名时输入形式不能为 x.ToString()(不要输入圆括号),也不能把它传送给委托变量。输入圆括号调用一个方法。调用 x.ToString() 方法会返回一个不能赋予委托变量的字符串对象。只能把方法的地址赋予委托变量。
***委托的一个特征是它们的类型时安全的,可以确保被调用的方法的签名是正确的,但有趣的是,它们不关心在什么类型的对象上调用该方法,甚至不考虑该方法时静态方法,还是实例方法。
***给定委托的实例可以引用任何类型的任何对象上的实例方法或静态方法——只要方法的签名匹配于委托的签名即可。
实例:
using System;
namespace DelegateTest
{
class Program
{
delegate double DoubleOp(double x);//定义委托
static void Main(string[] args)
{
DoubleOp[] operations = { MathOperations.MultiplyByTwo,MathOperations.Square};
for(int i = 0; i < operations.Length; i++)
{
Console.WriteLine("Using Operations[{0}]:",i);
Console.WriteLine(operations[i]);
ProcessAndDisplayNumber(operations[i], 3.0);
ProcessAndDisplayNumber(operations[i], 7.32);
ProcessAndDisplayNumber(operations[i], 16.23);
Console.WriteLine();
}
}
static void ProcessAndDisplayNumber(DoubleOp actions, double value)
{
double result = actions(value);
Console.WriteLine("Value is {0},result of operation is {1}", value, result);
}
}
}
1.2.3 Action<T> 和 Func<T> 委托
泛型 Ation<T> 委托表示引用一个 void 返回类型的方法。这个委托类存在不同的变体,可以传递至多16种不同的参数类型。没有泛型参数的Action的类可以调用没有参数的方法。
Action<in T> 或 Action< in T1, in T2>
Func<T> 委托可以以类似的方式使用。Func<T>允许调用带返回类型的方法。与Action<T> 类似,Func<T> 也定义了不同的变体,至多也可以传递16个参数类型和一个返回类型。
Func<in T,out TResult> 或 Func<in T1,in T2,in T3,in T4,out TResult>
上节的 delegate double DoubleOp(double x); 也可以使用 Func<in T, out TResult>委托。
如下所示:
Func<double,double> operations={MathOperations.MultiplyByTwo,MathOperations.Square};
调用过程如下
static void ProcessAndDisplayNumber(Func<double,double> action,double value)
{
double result=action(value);
Console.WriteLine("Value is {0},result of operations is {1}",value ,result);
}
冒泡排序示例:(BubbleSorter示例)
using System;
using System.Collections.Generic;
namespace DelegateBubbleSort
{
class BubbleSort
{
static public void Sort<T>(IList<T> sortArray,Func<T,T,bool> comparison)
{
bool swapped = true;
do
{
swapped = false;
for (int i = 0; i < sortArray.Count -1; i++)
{
if (comparison(sortArray[i + 1], sortArray[i]))
{
T temp = sortArray[i];
sortArray[i] = sortArray[i + 1];
sortArray[i + 1] = temp;
swapped = true;
}
}
} while (swapped);
}
}
}
using System;
namespace DelegateBubbleSort
{
class Employee
{
public Employee()
{
}
public Employee(string name,decimal salary)
{
this.Name = name;
this.Salary = salary;
}
public string Name
{
get;
private set;
}
public decimal Salary
{
get;
private set;
}
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;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DelegateBubbleSort
{
class Program
{
static void Main(string[] args)
{
Employee[] empolyee =
{
new Employee("Bug",1000),
new Employee("PSD",10000),
new Employee("SSSD",1500)
};
BubbleSort.Sort(empolyee, Employee.CompareSalary);
foreach (var emp in empolyee)
{
Console.WriteLine(emp);
}
}
}
}
1.2.4 多播委托
前面使用的每个委托都只包含一个方法调用。调用委托的次数与调用方法的次数相同。如果要调用多个方法,就需要多次显式调用这个委托。但是,委托也可以包含多个方法,这种委托称为多播委托。
如果调用多播委托,就可以按顺序连续调用多个方法,为此,委托的签名就必须返回void;否则,就只能得到委托调用的最后一个方法的结果。
【 通过一个委托调用多个方法还可能导致一个大问题。多播委托包含一个逐个调用的委托集合,如果通过委托调用的其中一个方法抛出一个异常,整个迭代就会停止。】
using System;
namespace MutiplyDelegate
{
class Program
{
static void One()
{
Console.WriteLine("One!");
throw new Exception("Error in One!");
}
static void Two()
{
Console.WriteLine("Two!");
}
static void Main(string[] args)
{
Action dl = One;
dl += Two;
try
{
dl();
}
catch (Exception )
{
Console.WriteLine("I caught you!");
}
}
}
}
//结果
One!
I caught you!
上面例子中,委托只调用了第一个方法,因为第一个方法抛出了一个异常。所以委托的迭代会停止,不再调用Two()方法。没有指定调用方法的顺序时,结果会有所不同。
在这种情况下,为了避免这个问题,应自己迭代方法列表。 Delegate类定义 GetInvocationList()方法,它返回一个Delegate 对象数组。源码如下:
using System;
namespace MutiplyDelegate
{
class Program
{
static void One()
{
Console.WriteLine("One!");
throw new Exception("Error in One!");
}
static void Two()
{
Console.WriteLine("Two!");
}
static void Main(string[] args)
{
//Action dl = One;
//dl += Two;
//try
//{
// dl();
//}
//catch (Exception )
//{
// Console.WriteLine("I caught you!");
//}
Action dl = One;
dl += Two;
Delegate[] delegates = dl.GetInvocationList();
foreach (Action d in delegates)
{
try
{
d();
}
catch (System.Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
}
//结果
One!
Error in One!
Two!
1.2.5匿名方法
通过匿名方法是用作委托的参数的一段代码。
用匿名方法定义委托的语法与前面的定义并没有区别。但在实例化时,就有区别了。
using System;
namespace MutiplyDelegate
{
class Program
{
static void Main(string[] args)
{
string mid = ", middle part,";
Func<string, string> anonDel = delegate(string param)
{
param += mid;
param += " and this was added to the string .";
return param;
};
Console.WriteLine(anonDel("Strat of !"));
}
}
}
结果:
Strat of !, middle part, and this was added to the string .
Func<string,string> 委托接受一个字符串参数,返回一个字符串。anonDel 是这种委托类型的变量。不是把方法名赋予这个变量,而是使用一段简单的代码: 它前面是关键字 delegate ,后面是一个字符串参数。在调用委托时,把一个字符串作为参数传递,将返回的字符串输出到控制台上。
【在使用匿名方法时,必须遵循两条规则。
- 在匿名方法中不能使用跳转语句(break,goto,continue)跳到该匿名方法的外部,反之亦然:匿名方法外部的跳转语句不能呢个跳到匿名方法的内部。
- 在匿名方法内部不能访问不安全的代码。另外也不能访问在匿名方法外部使用的 ref 和 out参数 。但可以使用在匿名方法外部定义的其他变量】
如果需要用匿名方法多次编写同一个功能,就不要使用匿名方法。