.NET C# 声明、实例化和使用委托以及委托在 C# 中的发展
本文内容
- 委托和泛型委托
- 委托发展:C# 中委托的发展
- 泛型委托
- 委托
- 声明(定义)委托
- 实例化委托
- 调用委托
- 用 Lambda表达式创建和实例化委托
- .NET 提供的委托
- Action 委托
- Func 委托
- Predicate 委托
- 参考资料
- 修改记录
下载 Deom
下载更多 Demo
委托和泛型委托
委托实现了函数指针,这个函数指针跟 C 的函数指针不同,它是类型安全的,确保被调用的方法签名是正确的。只要方法签名跟委托签名匹配,给委托的实例可以是实例方法,或是静态方法。
为什么要有这个东西?我们对把数据作为函数参数很熟悉,但有时,某个方法的操作不是针对数据,而是针对另一个方法。比如,线程,用线程去执行一个方法,或是代码段;再比如,事件,事件是委托的特例,等等。
委托发展:C# 中委托的发展
- C# 1.0 中,通过用在其他地方定义的方法显式初始化委托来创建委托的实例。
- C# 2.0 引入了匿名方法(anonymous method)的概念,用匿名方法初始化委托,在委托中执行未命名的内联语句块。
- C# 3.0 引入了 Lambda 表达式,与匿名方法的概念类似,但更具表现力并且更简练。匿名方法和 Lambda 表达式统称为“匿名函数”,类似闭包(Closure)特性。
- 通常,针对 .NET Framework 3.5 及更高版本应使用 Lambda 表达式。
下面的示例演示了从 C# 1.0 到 C# 3.0 委托创建过程的发展:
示例1:
namespace MyDelegate
{
class Program
{
delegate void TestDelegate(string s);
static void M(string s)
{
System.Console.WriteLine(s);
}
static void Main(string[] args)
{
// C# 1.0: 最初的委托语法,用一个方法名初始化委托.
TestDelegate testdelA = new TestDelegate(M);
// C# 2.0: 用内联代码初始化委托,这个内联代码成为“匿名方法”.
// 这个方法把一个字符串作为输入参数.
TestDelegate testDelB = delegate(string s) { System.Console.WriteLine(s); };
// C# 3.0: 用 Lambda 表达式初始化委托.
// Lambda 表达式也把一个字符串(x)作为输入参数.
// 编译器可以推断 x 的数据类型.
TestDelegate testDelC = (x) => { System.Console.WriteLine(x); };
// 调用委托.
testdelA("Hello. My name is M and I write lines.");
testDelB("That's nothing. I'm anonymous and ");
testDelC("I'm a famous author.");
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}
}
运行结果:
Hello. My name is M and I write lines.
That's nothing. I'm anonymous and
I'm a famous author.
Press any key to exit.
泛型委托
示例 1 也可改写成泛型形式。如下所示:
示例 2:
namespace MyGenericDelegate
{
class Program
{
delegate void TestGenericDelegate<T>(T s);
static void GenericM<T>(T s)
{
System.Console.WriteLine(s);
}
static void Main(string[] args)
{
// C# 1.0
TestGenericDelegate<int> testGenericDelA = new TestGenericDelegate<int>(GenericM);
// C# 2.0
TestGenericDelegate<string> testGenericDelB = delegate(string s) { System.Console.WriteLine(s); };
// C# 3.0
TestGenericDelegate<double> testGenericDelC = (x) => { System.Console.WriteLine(x); };
// 调用委托.
testGenericDelA(123456);
testGenericDelB("That's nothing. I'm anonymous and ");
testGenericDelC(123.456);
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}
}
运行结果:
123456
That's nothing. I'm anonymous and
123.456
Press any key to exit.
委托
以示例 1 为例:
- 声明(定义)委托
delegate void TestDelegate(string s);
每个委托描述了方法签名和返回类型等全部细节。如 TestDelegate 定义方法有一个 string 类型的参数 s,并且返回 void 类型。
可以在任何地方定义委托,跟定义一个类类似。委托也可以有访问修饰符。
- 实例化委托
TestDelegate testdelA = new TestDelegate(M);
声明委托后,必须用某个方法实例化这个委托。用方法 M 去实例化委托 testdelA。
声明(定义)和实例化委托,有点类似一个类,类也需要定义,并实例化。
委托在语法上,总是带有一个参数的构造函数,这个参数就是委托引用的方法。也就是说,函数指针必须指向一个方法。
- 调用委托
testdelA("Hello. My name is M and I write lines.");
实例化委托后,通过委托对象的名称(后面是传递给委托的参数)调用委托对象。
委托也可以组合、移除,如下所示:
namespace MyDelegate
{
delegate void D(int x);
class C
{
public static void M1(int i)
{
Console.WriteLine("C.M1: " + i);
}
public static void M2(int i)
{
Console.WriteLine("C.M2: " + i);
}
public void M3(int i)
{
Console.WriteLine("C.M3: " + i);
}
}
}
用如下代码测试:
D cd1 = new D(C.M1);
cd1(-1); // call M1
D cd2 = new D(C.M2);
cd2(-2); // call M2
D cd3 = cd1 + cd2;
cd3(10); // call M1 then M2
cd3 += cd1;
cd3(20); // call M1, M2, then M1
C c = new C();
D cd4 = new D(c.M3);
cd3 += cd4;
cd3(30); // call M1, M2, M1, then M3
cd3 -= cd1; // remove last M1
cd3(40); // call M1, M2, then M3
cd3 -= cd4;
cd3(50); // call M1 then M2
cd3 -= cd2;
cd3(60); // call M1
cd3 -= cd2; // impossible removal is benign
cd3(60); // call M1
cd3 -= cd1; // invocation list is empty so cd3 is null
// cd3(70); // System.NullReferenceException thrown
cd3 -= cd1; // impossible removal is benign
- 用 Lambda 表达式创建和实例化委托。
Func<int, bool> myFunc = x => x == 5;
bool result = myFunc(4); // returns false of course
其中,Func<int, bool> 是.NET 提供的已封装好的委托,用于以参数形式传递的方法,必须返回值。这样,就不用显式声明定义委托。该委托输入参数为 int,返回类型为 bool。
.NET 提供的委托
Action 委托
该委托以参数形式传递一个执行某操作的方法,不返回值。Action 委托有如下几个重载:
- Action 委托
- Action<T> 委托
- Action<T1, T2> 委托
- Action<T1, T2, T3> 委托
- Action<T1, T2, T3, T4> 委托
.NET framework 4.0 提供的重载更多。可提供 16 个输入参数。
示例 3:以 Action<T> 带一个参数的委托为例。
using System;
namespace MyAction
{
class Program
{
// 声明委托
delegate void DisplayMessage(string message);
static void Main(string[] args)
{
// 用 ShowWindowsMessage,采用命名方法实例化 DisplayMessage 委托
DisplayMessage messageTargetA = new DisplayMessage(ShowWindowsMessage);
DisplayMessage messageTargetB = ShowWindowsMessage;
// 用 ShowWindowsMessage,采用命名方法实例化 Action 委托
Action<string> messageTargetC = ShowWindowsMessage;
// 用 ShowWindowsMessage,采用匿名方法实例化 Acton 委托
Action<string> messageTargetD = delegate(string s) { ShowWindowsMessage(s); };
// 用 ShowWindowsMessage,采用 Lambda 表达式实例化 Acton 委托
Action<string> messageTargetE = s => ShowWindowsMessage(s);
messageTargetA("Hello, World!");
messageTargetB("Hello, World!");
messageTargetC("Hello, World!");
messageTargetD("Hello, World!");
messageTargetE("Hello, World!");
System.Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
private static void ShowWindowsMessage(string message)
{
System.Console.WriteLine(message);
}
}
}
运行结果:
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Press any key to exit.
该示例最简单的形式也可写成:
Action<string> messageTarget = s => System.Console.WriteLine(s);
messageTarget("Hello, World!");
System.Console.WriteLine("Press any key to exit.");
Console.ReadKey();
Func 委托
该委托以参数形式传递的方法,必须返回值。Func 委托有如下几个重载:
- Func<TResult> 委托
- Func<T, TResult> 委托
- Func<T1, T2, TResult> 委托
- Func<T1, T2, T3, TResult> 委托
- Func(<T1, T2, T3, T4, TResult> 委托
.NET framework 4.0 提供的重载更多。可提供 16 个输入参数。
示例 4:以 Func(T, TResult) 待一个参数的委托为例。
using System;
namespace MyFunc
{
delegate string ConvertMethod(string inString);
class Program
{
static void Main(string[] args)
{
// 用 UppercaseString,以命名方法实例化委托
ConvertMethod convertMethA = UppercaseString;
// 用 UppercaseString,以命名方法实例化 Func 委托
Func<string, string> convertMethB = UppercaseString;
// 以匿名方法实例化 Func 委托
Func<string, string> convertMethC = delegate(string s) { return s.ToUpper(); };
Func<string, string> convertMethD = delegate(string s) { return UppercaseString(s); };
// 以 Lambda 表达式实例化 Func 委托
Func<string, string> convertMethE = s => s.ToUpper();
System.Console.WriteLine(convertMethA("Dakota"));
System.Console.WriteLine(convertMethB("Dakota"));
System.Console.WriteLine(convertMethC("Dakota"));
System.Console.WriteLine(convertMethD("Dakota"));
System.Console.WriteLine(convertMethE("Dakota"));
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
private static string UppercaseString(string inputString)
{
return inputString.ToUpper();
}
}
}
运行结果:
DAKOTA
DAKOTA
DAKOTA
DAKOTA
DAKOTA
Press any key to exit.
该示例最简单的形式也可写成:
Func<string, string> convertMeth = s => s.ToUpper();
System.Console.WriteLine(convertMeth("Dakota"));
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
Predicate 委托
该委托定义一组条件并确定指定对象是否符合这些条件的方法。此委托由 Array 和 List<T> 类的几种方法使用,用于在集合中搜索元素。
示例 5:演示在数组中查找第一个 X*Y>100000 的点。
using System;
using System.Collections.Generic;
namespace MyPredicate
{
class Program
{
static void Main(string[] args)
{
Point[] points = {
new Point(){X = 100,Y = 200}, new Point(){X = 150,Y = 250},
new Point(){X = 250,Y = 375}, new Point(){X = 275,Y = 395},
new Point(){X = 295,Y = 450}, new Point(){X = 290,Y = 451}
};
Point first = Array.Find(points, ProductGT10);
Console.WriteLine("Found: X = {0}, Y = {1}", first.X, first.Y);
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
class Point
{
public int X { get; set; }
public int Y { get; set; }
}
private static bool ProductGT10(Point p)
{
if (p.X * p.Y > 100000)
return true;
else
return false;
}
}
}
也可以这些写:
Point[] points = {
new Point(){X = 100,Y = 200}, new Point(){X = 150,Y = 250},
new Point(){X = 250,Y = 375}, new Point(){X = 275,Y = 395},
new Point(){X = 295,Y = 450}, new Point(){X = 290,Y = 451}
};
Point first = Array.Find(points,
(p) =>
{
if (p.X * p.Y > 100000)
return true;
else
return false;
});
Console.WriteLine("Found: X = {0}, Y = {1}", first.X, first.Y);
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
说明:无需显示创建委托,或是指定泛型方法的参数类型,因为编译器会根据上下文自己确定。
参考资料
- MSDN 声明、实例化和使用委托
- MSDN 提供的委托
- 事件(C# 编程指南)
- 委托(C# 编程指南)
- C# 3.0 Cookbook, Third Edition: More than 250 solutions for C# 3.0 programmers-Delegates, Events, and Lambda Expressions
- C# 3.0: Master the fundamentals of C# 3.0-Delegates and Events
- 程序设计_猫老鼠主人
修改记录
- 2015年1月29日 【UPDATE】