介绍.NET中的委派(一) |
----微软 .NET平台系列文章之四 |
|
|
回调函数 回调函数的确是至今为止最有用的编程机制之一。C运行时的qsort函数利用回调函数对数组元素进行排序。在Windows中,回调函数更是窗口过程,钩子过程,异步过程调用,以及目前Microsoft .NET框架所必需的,在整个回调过程中自始至终地使用回调方法。人们可以注册回调方法以获得加载/卸载通知,未处理异常通知,数据库/窗口状态修改通知,文件系统修改通知,菜单项选择,完成的异步操作通知,过滤一组条目等等。 在C/C++中,一个函数的地址就是内存地址。这个地址不会附带任何其它赋加信息,如函数的参数个数,参数类型,函数的返回值类型以及这个函数的调用规范。简言之,C/C++回调函数不是类型安全的。 在.NET框架中,回调函数所受到的重用与它在Windows非受控编程中一样。不同的是在.NET框架中提供了一种叫委派(delegates)的类型安全机制。我们先来研究一下委派的声明。下面的代码展示了如何声明,创建和使用委派: |
//
using System;
using System.WinForms; // 在beta2版本中为:System.Windows.Forms
using System.IO;
class Set {
private Object[] items;
public Set(Int32 numItems) {
items = new Object[numItems];
for (Int32 i = 0; i < numItems; i++)
items[i] = i;
}
// 定义 Feedback,类型为delegate
// (注意: 这个类型在Set类中是嵌套的)
public delegate void Feedback(
Object value, Int32 item, Int32 numItems);
public void ProcessItems(Feedback feedback) {
for (Int32 item = 0; item < items.Length; item++) {
if (feedback != null) {
// 一旦指定了回调,便调用它们
feedback(items[item], item + 1, items.Length);
}
}
}
}
class App {
[STAThreadAttribute]
static void Main() {
StaticCallbacks();
InstanceCallbacks();
}
static void StaticCallbacks() {
// 创建一个Set对象,其中有五个项目
Set setOfItems = new Set(5);
// 处理项目,feedback=null
setOfItems.ProcessItems(null);
Console.WriteLine();
// 处理项目,feedback=Console
setOfItems.ProcessItems(new Set.Feedback(App.FeedbackToConsole));
Console.WriteLine();
// 处理项目,feedback =MsgBox
setOfItems.ProcessItems(new Set.Feedback(App.FeedbackToMsgBox));
Console.WriteLine();
// 处理项目,feedback = console AND MsgBox
Set.Feedback fb = null;
fb += new Set.Feedback(App.FeedbackToConsole);
fb += new Set.Feedback(App.FeedbackToMsgBox);
setOfItems.ProcessItems(fb);
Console.WriteLine();
}
static void FeedbackToConsole(
Object value, Int32 item, Int32 numItems) {
Console.WriteLine("Processing item {0} of {1}: {2}.",
item, numItems, value);
}
static void FeedbackToMsgBox(
Object value, Int32 item, Int32 numItems) {
MessageBox.Show(String.Format("Processing item {0} of {1}: {2}.",
item, numItems, value));
}
static void InstanceCallbacks() {
//创建一个Set对象,其中有五个元素
Set setOfItems = new Set(5);
// 处理项目,feedback=File
App appobj = new App();
setOfItems.ProcessItems(new Set.Feedback(appobj.FeedbackToFile));
Console.WriteLine();
}
void FeedbackToFile(
Object value, Int32 item, Int32 numItems) {
StreamWriter sw = new StreamWriter("Status", true);
sw.WriteLine("Processing item {0} of {1}: {2}.",
item, numItems, value);
sw.Close();
}
}
// |
注意代码最上面的Set类。假设这个类包含一组将被单独处理的项目。当你创建Set对象时,将它要操纵的项目数传递给它的构造函数。然后构造函数再创建对象(Objects)数组并初始化每一个整型值。 另外,Set类定义了一个公共的委派,这个委派指出某个回调函数的签名。在这个例子中,委派Feedback 确定了一个带三个参数的方法(第一个参数为Object,第二和第三个参数都是Int32类型)并且返回void。在某种意义上,委派很像C/C++中表示某个函数地址的类型定义(typedef)。 此外,Set类定义了一个公共方法:ProcessItems。这个方法有一个参数feedback——一个对委派Feedback 对象的引用。ProcessItems迭代遍历所有的数组元素,并且针对每一个元素调用回调方法(由feedback变量指定哪一个会调方法),这个回调方法被传递,从而以不同的方式处理回调方法所传递的项目值,项目数量以及数组中的元素数目。可以看出回调方法能以它选择的任何方式处理每一个项目。 |
使用委派调用静态方法 StaticCallbacks方法示范了用各种不同方式的回调委派。这个方法首先构造一个Set对象,告诉它对象创建有五个对象元素的数组。然后调用ProcessItems,在第一个调用中,它的feedback参数为null。ProcessItems呈现一个方法,这种方法为每一个Set操纵的项目实现某种动作。在第一个例子中,因为feedback参数是null,在处理每一个项目时不调用任何回调方法。 第二个例子中创建了一个新的Set.FeedBack委派对象,这个委派对象是一个方法包装器,允许方法的调用是经由这个包装器间接调用。对于类型FeedBack的构造器来说,方法的名字(App.FeedBackConsole)被作为构造器的参数传递;这就表示方法被包装。然后,从new操作符返回的引用被传到ProcessItems。现在,当执行ProcessItems时,它会调用App类型的FeedbackToConsole方法处理集合中的每一个项目。FeedbackToConsole简单地将一个串输出到控制台,表示哪个项目被处理了以及这个项目的值是什么。 第三个例子与第二个例子基本相同。唯一的差别是Feedback委派对象包装的是另一个方法:App.FeedbackToMsgBox。这个方法建立一个串,用这个串表示哪个项目被处理以及这个项目的值是什么。然后将这个串显示在一个信息框中。 第四个例子也是静态调用的最后一个例子示范了如何将委派链接在一起形成一个链。在这个例子中,首先创键一个Feedback委派对象的引用变量fb,并将它初始化为null。这个变量指向委派链表的头。Null值表示链表中没有节点。然后,构造Feedback委派对象,由这个对象包装对App FeedbackToConsole方法的调用。C#中,+=操作符被用于将对象添加到fb引用的链表中。Fb此时指向链表的头。 最后,构造另一个Feedback委派对象,由这个对象包装对App FeedbackToMsgBox方法的调用。C#中的+=操作符又一次被用于将对象添加到fb引用的链表中,并且fb被新的链表的头更新。现在,当执行ProcessItems时,传递给它的是Feedback委派链表的头指针。在ProcessItems内部,调用回调方法的代码行实际上终止调用所有的在链表中由委派对象包装的回调方法。也就是说,对于被迭代的每一个项目,都会调用FeedbackToConsole,接着马上调用FeedbackToMsgBox。在后续文章中我将详细讨论委派链的处理机制。 有一点很重要,那就是在这个例子中每件事情都是类型安全的,例如,当构造Feedback委派对象时,编译器保证App的FeedbackToConsole和FeedbackToMsgBox方法都具备确切的原型,像由Feedback委派定义的一样。既两个方法必须有三个参数(Object,Int32和Int32),并且两个方法必须有相同的返回类型(void)。如果方法原型不匹配,则编译器将发出下面的出错信息:“error CS0123:The signature of method 'App.FeedbackToMsgBox()' does not match this delegate type。”——意思是App.FeedbackToMsgBox()方法的签名与委派的类型不匹配。 |
调用实例方法 前面我们讨论了如何使用委派调用静态方法。但是委派还能被用于调用特定对象的实例方法。在调用实例方法时,委派需要知道这个它要用方法操作的对象的实例。 为了理解实例方法的回调机制,让我们回头看看前面代码中的InstanceCallbacks方法。这段代码与静态方法的情形极其相似。注意在Set对象被创建之后,App对象被创建。这个App对象仅仅是创建而已,处于示例目的没有其它内容。当新的Feedback委派对象被创建的时候,它的构造齐备传到appobj.FeedbackToFile。这将导致这个委派包装对FeedbackToFile方法的引用,FeedbackToFile是个实例方法(非静态)。当这个实例方法被调用时,由appobj引用的对象被操作(作为隐藏传递参数)。FeedbackToFile方法的作用有点像FeedbackToConsole 和 FeedbackToMsgBox,不同的是它打开一个文件并将处理的项目串添加到文件尾。
揭开委派的神秘面纱 从表面上看,委派好像很容易使用:用C#委派关键字定义,用类似new操作符的方式构造它们的实例, 用类似方法调用的语法调用回调方法(不同的是不使用方法名,而是使用指代委派对象的变量)。 然而,委派的实际运行机制要比前述例子中所描述的过程要复杂的多。编译器和公共语言运行时(CLR)在幕后所做的许多处理隐藏了这些复杂性,在这一部分中,我们将集中精力来讨论编译器和CLR是如何协同工作实现委派机制的。这些知识将极大地丰富你对委派的理解并且这些知识将告诉你如何有效地使用它们。我们还将涉及到一些在编程中能用到的委派的附加特性。 我们还是从下面这行代码开始: |
public delegate void Feedback(
Object value, Int32 item, Int32 numItems); |
当编译器看到之一行代码时,它会产生一个完整的类定义,这个定义的代码会像下面这个样子: |
//
public class Feedback : System.MulticastDelegate {
// 构造器
public Feedback(Object target, Int32 methodPtr);
// 方法与源代码描述的原型相同
public void virtual Invoke(
Object value, Int32 item, Int32 numItems);
// 方法允许被异步回调,后继文章将讨论这些方法
public virtual IAsyncResult BeginInvoke(
Object value, Int32 item, Int32 numItems,
AsyncCallback callback, Object object);
public virtual void EndInvoke(IAsyncResult result);
}
// |
事实上,通过使用ILDasm.exe程序检查结果模块(如图三),你能发现编译器确实自动产生了这个类。 |
<?XML:NAMESPACE PREFIX = V /><shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"><stroke joinstyle="miter"><formulas><f eqn="if lineDrawn pixelLineWidth 0"><f eqn="sum @0 1 0"><f eqn="sum 0 0 @1"><f eqn="prod @2 1 2"><f eqn="prod @3 21600 pixelWidth"><f eqn="prod @3 21600 pixelHeight"><f eqn="sum @0 0 1"><f eqn="prod @6 1 2"><f eqn="prod @7 21600 pixelWidth"><f eqn="sum @8 21600 0"><f eqn="prod @7 21600 pixelHeight"><f eqn="sum @10 21600 0"></f></f></f></f></f></f></f></f></f></f></f></f></formulas><path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"><?XML:NAMESPACE PREFIX = O /><lock v:ext="edit" aspectratio="t"></lock></path></stroke></shapetype><shape id="_x0000_i1025" style="HEIGHT: 171pt; WIDTH: 375pt" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/FrontPageTempDir/wpe2.gif" o:title="netfig030401"></imagedata></shape> |
图三 检查编译器产生的类 |
在这个例子中,编译器已经定义了一个叫Feedback的从System.MulticastDelegate类型派生的类,它是在框架类库(Framework Class Library)中定义的。要知道,所有委派类型都是从MulticastDelegate派生出来的。在这个例子中,Feedback类是公共(public)类型的,因为在源代码中它的类型被定义为public。如果用私有(private)或者受保护的(protected)类型定义,则由编译器产生的Feedback类也将是私有或者受保护的类型。你应该注意到委派类可能会在某个类中定义(如例子中Feedback就是在Set类中定义的);委派也可能在被定义为全局型。从本质上说,可以将委派看成是类,可以在定义类的任何地方定义委派。 因为所有的委派都派生于MulticastDelegate,它们继承了MulticastDelegate的域,属性和方法。在所有这些成员中,你要特别注意三个私有(private)域: 用于委派类型的私有域: |
域 |
类型 |
描述 |
_target |
System.Object |
指回调函数被调用时应该操作的对象。用于实例方法回调 |
_methodPtr |
System.Int32 |
内部整型,CLR用它来标示被回调的方法 |
_prev |
System.MulticastDelegate |
指另一个委派对象,通常为null |
|
所有的委派都有代两个参数的构造器:一个参数是对象引用,一个是指代回调方法的整型。但是,如果你检查源代码,就会发现明白诸如App.FeedbackToConsole 或 appobj.FeedbackToFile的传递使用值进行的。你的敏感会告诉你这个代码不能编译! 然而,编译器知道某个委派被创建,同时编译器解析源代码以决定引用哪个对象和方法。对象引用被传递为目标参数,并且用某个特定的Int32值(从某个MethodDef或MethodRef元数据符号获得)标示的方法被传递为methodPtr参数。对于静态方法,null被传递为目标参数。在构造器内部,这两个参数被存储在它们对应的私有(private)域中。 另外,构造器将这个域置为null。这个域被用来创建一个MulticastDelegate对象链表。现在我们暂时忽略_prev域,在后续文章中将会详细讨论有关它的内容。 每一个委派对象实际上就是一个方法包装器,当方法被调用时,受作用的对象被操作。MulticastDelegate类定义两个只读公共实例属性:Target和Method。给定一个委派对象引用,你就可以查询到它的这些属性。如果方法被回调,Target属性返回一个对将要操作的对象的引用。如果方法是静态的,则Target返回null。Method属性返回标示回调方法的System.Reflection.MethodInfo对象。 你可以用几种方式使用这些信息。一种方式是检查是否某个委派对象引用特定类型的实例方法: |
//
Boolean DelegateRefersToInstanceMethodOfType(
MulticastDelegate d, Type type) {
return((d.Target != null) && d.Target.GetType == type);
}
// |
你还应该编写代码检查是否回调方法由专门的名字(如FeedbackToMsgBox): |
//
Boolean DelegateRefersToMethodOfName(
MulticastDelegate d, String methodName) {
return(d.Method.Name == methodName);
}
// |
现在你知道了如何构造委派对象,下面让我们来谈谈回调方法是如何被调用的。为方便起见,我们还是使用 Set类中的ProcessItems: |
//
public void ProcessItems(Feedback feedback) {
for (Int32 item = 1; item <= items.Length; item++) {
if (feedback != null) {
// 如果指定任何回调,则调用它们
feedback(items[item], item, items.Length);
}
}
}
// |
注释行下面的那一行代码就是调用回调方法。仔细看看代码,它调用feedback函数并传递三个参数。但是feedback是不存在的。再一次指出,编译器知道feedback是个引用某个委派对象的变量,并且编译器会产生实际的代码来调用委派对象的Invoke方法。换句话说,编译器看到下面这行代码后: |
feedback(items[item], item, items.Length); |
编译器产生的结果与下面这行源代码产生的结果一样: |
feedback.Invoke(items[item], item, items.Length); |
事实上,通过使用ILDasm.exe程序检查ProcessItems代码结果(如图五),你能发现这一点。 |
<shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"><stroke joinstyle="miter"><formulas><f eqn="if lineDrawn pixelLineWidth 0"><f eqn="sum @0 1 0"><f eqn="sum 0 0 @1"><f eqn="prod @2 1 2"><f eqn="prod @3 21600 pixelWidth"><f eqn="prod @3 21600 pixelHeight"><f eqn="sum @0 0 1"><f eqn="prod @6 1 2"><f eqn="prod @7 21600 pixelWidth"><f eqn="sum @8 21600 0"><f eqn="prod @7 21600 pixelHeight"><f eqn="sum @10 21600 0"></f></f></f></f></f></f></f></f></f></f></f></f></formulas><path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"><lock v:ext="edit" aspectratio="t"></lock></path></stroke></shapetype><shape id="_x0000_i1025" style="HEIGHT: 197.25pt; WIDTH: 285pt" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/FrontPageTempDir/wpe4.gif" o:title="netfig050401"></imagedata></shape> |
图五 分解后的 Set类的 ProcessItems |
图五显示了用于Set类型中ProcessItems方法的微软中介语言。其中红色的箭头指向的指令调用Set.Feedback的Invoke方法。如果你修改源代码来显式调用Invoke方法,C#编译器报错,出错信息为:“error CS1533: Invoke cannot be called directly on a delegate”——意思是Invoke不能针对某个委派被直接调用。C#不允许你显式调用Invoke(但是,但别的编译器可以)。 你会想起当编译器定义Feedback类的时候,它也定义了Invoke方法。当Invoke被调用时,它使用私有的_target和_methodPtr域来为特定对象调用希望的方法。注意Invoke方法的签名与委派的签名要完全匹配。也就是说,Feedback委派带三个参数并返回void,那么Invoke方法也必须带三个相同的参数并返回void。
|
相关推荐
如何做:在 Windows 2000 中实现 Kerberos 委派.doc 如何做:在 Windows 服务中驻留远程对象.doc 如何做:在具有 Enterprise Services 的 ASP.NET 中使用 DPAPI(用户存储).doc 如何做:在注册表中存储加密的连接...
在这个系列 中,我仅仅利用Sping.net这个框架向大家展示一下Ioc与AOP的强大功能(呵呵,其实写这段话的目的是因为“文章题目”牛皮吹得有点大了,给自 己个台阶下罢了)。 在这个系列中一共包含6个案例,从简单到...
第一部分 C#编程基础 第1章 C#入门 第2章 基本C#编程 第3章 表达式与运算符 第4章 判断、循环和预处理指令 第5章 面向对象的编程 第6章 类和对象 第7章 派生类 第8章 接口 第9章 字符串、日期、时间和时间段 第10章 ...
15.事件(事件处理)至事件(事件委派) 16.事件(事件委派)至事件(keyPress) 17.事件(keyPress)至滑动(slideUp) 18.滑动(slideUp)至Ajax(Ajax的XMLHttpRequest对象) 19.滑动(slideUp)至Ajax(load) 20.Ajax(load)至Ajax...
Rainbow项目是一个使用Microsoft ASP.NET 和 C#技术开发的全面的CMS 内容管理网站系统。 Rainbow项目当前支持29种语言,内容可以将委派给基于角色的组成员管理的会员,后者不需要懂HTML知识,Rainbow支持两步的审核...
该系列文章从第十期“.NET和C#”栏的两篇文章中引入委派的概念。构架中的应用程序如何声明、创建及使用委派。本文将介绍事件,它是委派应用得最多的场合。 事件允许某个对象通知其它对象,有个特定的事情已经发生...
实例376 CacheItemRemovedCallback委派缓存 移除通知 592 15.2 缓存数据的自动移除与依赖项的 应用 594 实例377 设定缓存优先级(CacheItemPrority) 594 实例378 设置Web应用程序高速缓存大小 596 实例379 设置...
使用ASP.NET Webforms的购物网站 使用的图层 实现所有业务功能的业务层 组成SQL的存储库层 数据层,它使用存储库层提供SQL包含来自数据库的数据 使用的设计模式 会话管理的外观模式 用于创建图层的工厂模式 用于将...
HTTP处理程序的一种类型,将HTTP响应消息的处理委派给另一个处理程序,称为内部处理程序。 它会检查每个请求是否需要许可,如果需要,它将在后台解决挑战,然后返回响应。 不使用Cloudflare的网站将得到正常处理。 ...
之所以出现StandIn,是因为最近在我们需要一个.NET本机解决方案来执行基于资源的约束委派。 但是,StandInSwift膨胀,包括许多舒适功能。 我想继续开发StandIn,以使自己更多地了解Directory Services编程,并希望...
博文《理解什么是委托、事件、Lambad表达式,从回调说起!》Demo
.net库(.net标准1.4和.net框架4.5.2),该文件从azure rest计费api读取数据。 它可以用于: 从Azure计费RateCard API读取数据 从Azure帐单使用情况API读取数据 从两个API读取数据,然后将它们合并以计算成本...
委派中的 Covariance 和 Contravariance 256 组合委派 (多播委派) 258 宣告、产生和使用委派 259 事件 264 订阅及取消订阅事件 265 发行符合 .NET Framework 方针的事件 267 在衍生类别中引发基类事件 271 实作界面...
.Net编程中最经常用的元素,事件必然是其中之一。...一、委托(有些书中也称为委派) 委托是什么呢?这个名字的意思已经赋予了我们想象的空间,你是编程的,你现在正在写一个ASP.NET网页,而JS是你不熟悉的,于是
使用ASP.NET MVC的银行应用程序 使用的图层 实现所有业务功能的业务层 组成SQL的存储库层 数据层,它使用存储库层提供SQL包含来自数据库的数据 使用的设计模式 会话管理的外观模式 用于创建图层的工厂模式 用于将...
但是对于.NET的客户端程序(Console,WPF,WinForms)是由CLR创建的单线程(主线程,且只创建一个线程)来启动。在该线程上可以创建其他线程。 图: 线程工作方式 多线程由内部线程调度程序管理,线程调度器通常是...
Visual Studio .NET(VS.NET)集成起来它可以让你在同个项目中包含测试类和功能类在相同项目中拥有测试类 和功能类就可以进行有效测试每次当个功能类改变时你不需要转换项目来测试在开发周期中你将测试思路方法 添加到...
文章:http://blog.csdn.net/hailushijie/article/details/8708753
C#技术内幕 全书共14章,每章分为两个部分,全面覆盖了C#语言基本的数据类型、编程概念知识,以及委派、事件、映射、公用...本书讲解翔实、示例具体,是C#编程人员不可或缺的参考手册,也适合.NET的中高级用户使用。
此资源适用于64位机 MicrosoftWebDeploy—一个采用全面的发布和部署机制的免费服务器技术。WebDeploy不仅仅让你...利用Web部署工具,管理员和委派的用户还可使用IIS管理器将ASP.NET和PHP应用程序部署到IIS7.0服务器。