在<关于“回调”的实现>一文中,我们探讨了使用委托实现回调。但对于回调的原型来讲,我们感到其使用上的繁琐,本文我们使用“匿名”方法来对其简化。
我们首先回顾一下,实现回调的主要步骤:
1、定义委托
2、定义调用者
3、定义执行体函数
我们来看下,通过匿名方法--对回调一文中的完整示例进行语法简化后的--程序如下:
using System; using System.Collections; class DBConnection { protected static int NextConnectionNbr = 1; protected string connectionName; public string ConnectionName { get { return connectionName; } } public DBConnection() { connectionName = "Database Connection " + DBConnection.NextConnectionNbr++; } } class DBManager { protected ArrayList activeConnections; public DBManager() { activeConnections = new ArrayList(); for (int i = 1; i < 6; i++) { activeConnections.Add(new DBConnection()); } } public delegate void EnumConnectionsCallback(DBConnection connection); public void EnumConnections(EnumConnectionsCallback callback) { foreach (DBConnection connection in activeConnections) { callback(connection); } } }; class InstanceDelegate { /*不再需要函数定义 public static void PrintConnections(DBConnection connection) { Console.WriteLine("[InstanceDelegate.PrintConnections] {0}", connection.ConnectionName); }*/ public static void Main() { DBManager dbManager = new DBManager(); Console.WriteLine("[Main] Instantiating the " + "delegate method"); /* DBManager.EnumConnectionsCallback _printConnections = new DBManager.EnumConnectionsCallback(PrintConnections);*/ //替换如下 DBManager.EnumConnectionsCallback _printConnections = delegate(DBConnection connection){ Console.WriteLine("[InstanceDelegate.PrintConnections] {0}", connection.ConnectionName); } Console.WriteLine("[Main] Calling EnumConnections " + "- passing the delegate"); dbManager.EnumConnections(_printConnections); Console.ReadLine(); } };
小结:
我们看到,于委托中忽略了函数名称的函数代码块,被称作anonymous method,即匿名方法。既然委托是同构函数的归类,就没有必要再为函数声明进行定义,所以,匿名方法凸显了委托的意义、简化了委托的使用。--匿名方法不需要使用函数名的原因是编译器会自动为delegate关键字处理函数名,而programmer并不需要这个函数名,而且有无意义不是很大。
更进一步,既然委托可以被“看作”对同类函数的语法抽象,我们可以用匿名方法忽略函数定义的header,那么我们当然可以忽略函数的名称而只保留参数,这就是“lambda语句”在委托中的应用。见实例:
using System; using System.Collections; class DBConnection { protected static int NextConnectionNbr = 1; protected string connectionName; public string ConnectionName { get { return connectionName; } } public DBConnection() { connectionName = "Database Connection " + DBConnection.NextConnectionNbr++; } } class DBManager { protected ArrayList activeConnections; public DBManager() { activeConnections = new ArrayList(); for (int i = 1; i < 6; i++) { activeConnections.Add(new DBConnection()); } } public delegate void EnumConnectionsCallback(DBConnection connection); public void EnumConnections(EnumConnectionsCallback callback) { foreach (DBConnection connection in activeConnections) { callback(connection); } } }; class InstanceDelegate { public static void Main() { DBManager dbManager = new DBManager(); Console.WriteLine("[Main] Instantiating the " + "delegate method"); /* DBManager.EnumConnectionsCallback _printConnections = delegate(DBConnection connection){ Console.WriteLine("[InstanceDelegate.PrintConnections] {0}", connection.ConnectionName);*/ //替换如下 DBManager.EnumConnectionsCallback _printConnections = connection=>{ Console.WriteLine("[InstanceDelegate.PrintConnections] {0}", connection.ConnectionName); } Console.WriteLine("[Main] Calling EnumConnections " + "- passing the delegate"); dbManager.EnumConnections(_printConnections); Console.ReadLine(); } };
小结:lambda的引入,产生了变量超出其范围而与委托的生存周期同步这一“现象”,这里我们暂称这种变量为外部变量(outer variable),这时系统不再视变量为固定的而是可移动的,但可使用fixed关键词来控制(fixed关键词的使用见后)。
最后,我们通过引入泛型来对该程序进行性能优化,示例如下:
using System; using System.Collections; using System.Collections.Generic; class DBConnection { protected static int NextConnectionNbr = 1; protected string connectionName; public string ConnectionName { get { return connectionName; } } public DBConnection() { connectionName = "Database Connection " + DBConnection.NextConnectionNbr++; } } class DBManager<T> { protected ArrayList activeConnections; public DBManager() { activeConnections = new ArrayList(); for (int i = 1; i < 6; i++) { activeConnections.Add(new DBConnection()); } } public delegate void EnumConnectionsCallback(T connection); public void EnumConnections(EnumConnectionsCallback callback) { foreach (T connection in activeConnections) { callback(connection); } } }; class InstanceDelegate { public static void Main() { DBManager dbManager = new DBManager(); Console.WriteLine("[Main] Instantiating the " + "delegate method"); DBManager.EnumConnectionsCallback _printConnections = connection=>{ Console.WriteLine("[InstanceDelegate.PrintConnections] {0}", connection.ConnectionName); } Console.WriteLine("[Main] Calling EnumConnections " + "- passing the delegate"); dbManager.EnumConnections(_printConnections); Console.ReadLine(); } };
小结:通过对DBManger引进泛型,使得负责数据库连接的DBConnection与委托部分的程序相分离。 这就是所谓的“泛型委托”。
总结:由此,我们通过“委托推理”(delegate inference)、“匿名方法”(anonymous method)、“lambda表达式(type inference)”,基本完成了回调一文中回调原型的简化。 效果就是取得客户端代码最优化,这也意味着良好的易读性。而lambda最重要的意义在于实现了算法复用(algorithm reusing),MS建议使用lambda取代匿名方法。
附,fixed keyword的使用
fixed 语句禁止垃圾回收器重定位可移动的变量。fixed 语句只能出现在不安全的上下文中。Fixed 还可用于创建固定大小的缓冲区。
fixed 语句设置指向托管变量的指针并在 statement 执行期间“钉住”该变量。如果没有 fixed 语句,则指向可移动托管变量的指针的作用很小,因为垃圾回收可能不可预知地重定位变量。C# 编译器只允许在 fixed 语句中分配指向托管变量的指针。
// assume class Point { public int x, y; }
// pt is a managed variable, subject to garbage collection.
Point pt = new Point();
// Using fixed allows the address of pt members to be
// taken, and "pins" pt so it isn't relocated.
fixed ( int* p = &pt.x )
{
*p = 1;
}
可以用数组或字符串的地址初始化指针:
fixed (int* p = arr) ... // equivalent to p = &arr[0]
har* p = str) ... // equivalent to p = &str[0]
只要指针的类型相同,就可以初始化多个指针:
fixed (byte* ps = srcarray, pd = dstarray) {...}
要初始化不同类型的指针,只需嵌套 fixed 语句:
fixed (int* p1 = &p.x)
{
d (double* p2 = &array[5])
{
// Do something with p1 and p2.
}
}
执行完语句中的代码后,任何固定变量都被解除固定并受垃圾回收的制约。因此,不要指向 fixed 语句之外的那些变量。
无法修改在 fixed 语句中初始化的指针。
在不安全模式中,可以在堆栈上分配内存。堆栈不受垃圾回收的制约,因此不需要被锁定。
// statements_fixed.cs
// compile with: /unsafe
using System;
class Point
{
public int x, y;
}
class FixedTest
{
// Unsafe method: takes a pointer to an int.
unsafe static void SquarePtrParam (int* p)
{
*p *= *p;
}
unsafe static void Main()
{
Point pt = new Point();
pt.x = 5;
pt.y = 6;
// Pin pt in place:
fixed (int* p = &pt.x)
{
SquarePtrParam (p);
}
// pt now unpinned
Console.WriteLine ("{0} {1}", pt.x, pt.y);
}
}
结果为:
25 6