您的位置:首页 > 编程语言 > C语言/C++

C++转向C#的疑惑:C#中类间通信方法初探

2008-07-22 22:21 337 查看
来源:zhuweisky的专栏http://blog.csdn.net/zhuweisky/archive/2005/07/06/415756.aspx

C#中没有自由函数也没有自由变量,所有的一切都必须封装在类中。在C++中,通过全局变量来进行函数间通信和类间通信是常用的方法,然而这在C#中却变成不可能任务,这的确让C++程序员苦恼。C#当然有它充分的理由不支持自由函数和自由变量,因为“自由”就暗示着冲突、不安全等等影响程序健壮性和可维护性的因素。当然有很多技术可以解决C#中的类间通信问题,本文就介绍其中的几种。

一.问题域涉及的主要Class



在介绍之前先作一下说明。一般我们可以把类间通信时需要的各相关数据元素封装成一个结构,我称之为“通信结构”,将其记为Information struct ,它是一个纯粹的C结构,即除了公共的数据成员外,什么都没有。当类间需要交换消息时就可以生成一个“通信结构”实例,然后在类间传递。另外我把提供信息的类称为Provider Class (以下简称为Provider),接受信息的类称为Master Class(以下简称为Master)。当然,两个相互通信的类可以互为Provider和Master。请注意,后面将出现Provider object和Master object,它们对应于是Provider和Master的实例。
Master从Provider获取所需信息主要有两种方式:正向获取和反向获取。每一种方式又有几种不同的实现方式。下面我将一一介绍。

二.正向获取信息

所谓“正向“获取信息,就是按照通常的直线思考方式来考虑,Master需要从Provider获取信息,就直接让Master访问Provider的某个成员。在这种方式下,Master处于主动地位,因为总是由它去主动拜访Provider,而Provider相对比较被动。
不出你所料,主要有以下几种方法。
1. Provider暴露一个成员变量或属性



Master 可以通过访问这个成员变量或属性得到所需数据。这种方法对于得到一些简单的非保密性信息是比较方便的,通常这些信息可以被Provider自己直接提供,并且不易发生变化,比如获取某个控件(当然是一个Provider object)的size或color等等。此方法的不利之处是,如果Provider所提供的数据是保密性的,那么这个安全性就得不到保证。

2. Provider暴露一个方法



如果所需的信息比较复杂(比如需要通过简单的计算才能得到),或经常处于变化之中,通过一个方法来提供这样的信息无疑是更好的选择。有两种实现方式:
(1)通过返回值
这个我们经常用,在这种情况下,Master还可以通过参数来和Provider进行更多的交流。如:
public Information GetInformation( 参数 ) ;

(2)通过ref或out参数
如果一个方法的返回值被用于其它目的,如判断方法是否执行成功等,此时可以用ref或out来将所需的信息从方法中“带出”。如:
public int GetInformation(out Information info) ;

或许你已经看出来了,正向获取信息的方式的主要问题在于,Master不知道Provider会于何时准备好所需的数据,也就是说,Master是在一个合适的时间拜访Provider吗?如果时机不恰当,显然就得不到正确的信息。
为了解决这个问题,我们可以采取反向获取信息的方式。

三.反向获取信息
所谓“反向”获取信息,就是Provider在准备好数据后,主动将数据提供给Master。与正向获取信息的方式相比较,可以看到,主――客关系发生了变化,现在是Provider主动去拜访Master。这就很自然地解决了正向获取信息方式的时机可能不成熟的问题。
在实际中,用的最多的就是反向获取信息的方式,普遍的情况是这样的:在Master需要数据时,创建一个Provider object,Provider object取得数据并作相关处理后将所需信息保存在一个Information object中,然后将该Information object提交给Master,自此该Provider object生命期结束,接着Master就可以处理得到的数据了。为了使读者有一个直观的认识,举个简陋的例子:

图1 Master object 图2 Provider object

这是一个简单图书信息管理系统,图1是Master object界面,图2是Provider object界面,当点击图1的“添加”按钮时就会弹出图2所示界面,当用户填写完信息后点击“提交按钮”,即可将一个新的图书信息添加到管理系统中。
此时,Information结构可如下设计:
public struct Information

{

public string book_name ;

public string author ;

public string date ;

}

1.通过类的静态成员变量数据通信



这是最简单的直接模拟C++中的利用全局变量进行通信的方法。我们把一个Information对象作为Master的静态成员变量,并将其修饰符设为public ,那么Provider object就可以对其进行写操作,当Provider写操作完成之后,Master就可以处理静态的Information对象了。现在,问题出现了――Master怎么知道Provider完成了写操作呢?回调函数!!!――这是C++程序员的第一反应,在C#中可以吗?可以,只不过C#提供的是一种安全的函数指针,叫委托。我们前面已经指出Provider对象通常都是由Master对象创建的,那么Master对象在创建Provider对象时可以传给Provider对象一个委托实例,当Provider完成写操作之后就可以通过此委托实例完成回调了。这个过程可如图3所示。

图3 通过类的静态成员变量进行类间通信

下面看看这种方法的主要C#伪码。
首先要定义委托:
public delegate void D_Callback() ;

再看Master类:
class Master

{

public static Information object_I ;

public void GetInformation()

{

D_Callback callback = new D_Callback(ProcessInformation) ;

Provider object_P = new Provider(callback) ;

......//如,显示object_P界面

}

private void ProcessInformation()

{

......//处理object_I

}

......

}

接下来看看Provider如何实现:

class Provider

{

private D_Callback back ;

public Provider(D_Callback yourback) //构造函数需以“回调函数”作为参数

{

this.back = yourback ;

}

//如当用户点击图2中的“提交”按钮时,将调用以下处理函数

private void button1_Click()

{

...... //访问Master.object_I ,并将信息写入

back() ;//触发回调函数调用,即调用Master中的ProcessInformation函数

this.close() ;// Provider对象完成任务,可以被GC回收了。

}

}

很简单,不是吗?是的,足够简单,它只不过是C++中用全局变量进行通信的C#版本。然而这种方法好吗?不好,这种方法仅仅是完成了任务,但是干得并不漂亮,甚至,这种实现是非常丑陋的。原因至少有两点:

首先,使Information对象成为Master的public静态对象就是非常不合适的,因为如果这样,不仅是Provider类而且所有的其它的类(包括含有恶意的类)都可以向这个静态区域写入信息,这就丧失了安全性。

其次,Master要将自己的成员函数(作为回调的函数)包装成委托传递给Provider类作为构造参数,这就导致了Master类和Provider类非常紧密的耦合,如同联体婴儿一样。如果我们想仅仅复用其中的一个类而不牵带另一个类是完全不可能的,这违背了OOD的基本原则。

很糟糕,不是吗?!让我们来看一个改良版本。

2.通过堆栈对象复制进行数据通信



在Provider对象中将获取数据存放到自己的私有Information对象中,然后用该Information对象调用从构造函数传递进来的委托,这样跳转到Master的回调函数时,因为Information是struct,而struct是值类型,所以将会在栈上复制这个Information型对象,其副本就可以被回调函数访问了。如此,信息就从Povider传递到Master了。
此过程可表示为图4所示的步骤。

图4 通过类的静态成员变量进行类间通信

再来看看代码有何改变:
public delegate void D_Callback(Information object_I) ;

//此处要将Information对象作为回调函数的参数

class Master

{

//不再拥有静态的Information成员

public void GetInformation()

{

D_Callback callback = new D_Callback(ProcessInformation) ;

Provider object_P = new Provider(callback) ;

......

}

private void ProcessInformation(Information object_I)

{

......//处理object_I的副本

}

......

}

接下来看看Provider的改变:

class Provider

{

private D_Callback back ;

//增加私有Information成员用以存放要传递的信息

private Information object_I ;



public Provider(D_Callback yourback)

{

this.back = yourback ;

}

private void button1_Click()

{

...... //将信息写入this.object_I

//以object_I作为参数调用Master中的ProcessInformation函数

back(this.object_I) ;

this.close() ;

}

}

这种方法通过堆栈对象复制而避免暴露一块公共的内存区域(public static member),

增强了安全性,但是Master和Provider之间的紧耦合性并没有减弱。难道没有完美的解决方案吗?有!那就是通过事件的发布――预定方式,采用这种方式,发布事件的类根本就不必知道预定事件的类的任何情况,从而将以前的Master和Provider之间的循环依赖转化为单向依赖(仅仅Master依赖于Provider),这就有效地降低了Master和Provider之间的耦合。

3.通过事件机制进行通信



事件机制为类间通信提供了一种优雅、高效、灵活的解决方案。

我们只需在Provider类中声明并发布事件,并在适当的时候触发事件;然后在Master类中预定事件,并定义事件处理函数就可以了。此方式下, Provider类不仅能为Master类所用,而且也能为其它需要得到同样信息的类使用。如此看来,Master类只是Provider类的一个客户(client)而已。整个通信过程可描述为图5所示的步骤。

图5 通过事件机制进行通信

看看是如何实现的。

首先一个委托的实例如果要成为事件,委托的原型声明必须是如下形式:

public delegate void D_Callback (object sender ,MyEventArgs e) ;

注意,MyEventArgs必须从EventArgs类继承,它是用来包装要传递的信息的,这里我们可以简单的如下实现:

public class MyEventArgs:System.EventArgs

{

public Information object_I;

}

接着看看Provider是如何发布事件并触发事件的:

class Provider

{

public event D_Callback PutOutInformation; //发布事件

private void button1_Click()

{

MyEventArgs args = new MyEventArgs() ;

...... //将信息写入args.object_I

PutOutInformationthisargs); //触发事件

this.close() ;

}

}

是不是简单、清晰、明了?!Provider根本就不需要知道Master类的存在。

再来看看Master类如何预定事件,以及事件发生时如何获得所需信息。

class Master

{

public void GetInformation()

{

Privoder object_P = new Privoder() ;

//预定事件

object_P. PutOutInformation



+= new D_Callback(this.ProcessInformation) ;

.....

}

private void ProcessInformation(object sender ,MyEventArgs e)

{

.....//处理e.object_I

}

......

}

上面的代码简单明了,就不用我再罗嗦了。

C#是一种完全面向对象的语言,相对于C++,它剔除了全局变量和全局函数,并且引入了事件、属性等面向对象编程的常用特性。由于没有了全局变量,所以在C#中必须寻找另外的方案来解决类间通信的问题,而C#提供的事件机制恰好是解决这类问题的一剂良方,虽然在C#中也可以模拟C++通过静态存储区或栈复制来完成通信,但这些方式给人的感觉比较笨拙不够优雅,所以,当你找不到更好的办法来实现类间通信时,就用事件机制吧。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: