Finally, we comes with the C# language. C# supports all OO features mentioned in previous articles in language level. So, our implementation is quite straight forward. Just check the source code in my skydrive: http://cid-481cbe104492a3af.office.live.com/browse.aspx/share/dev/TestOO. In the TestCSObject-{date}.zip file.

Lastly, I drew a simple table to compare and summarize the OO supports in the four leading languages:

C/gtk+ C++/Qt Java C#
Basic Features
Encapsulation library good design Y Y
Inheritance library Y Y Y
Polymorphism function pointer Y Y Y
Advanced Features
Property library library reflection feature Y
Meta Info library library annotations attributes
Event Driven library(signals) library(signals/slots) events/listeners delegates/events/handlers
Measurements (Using a test application to demostrate above 5 features)
Compiler mingw32-gcc/3.4 mingw32-g++/3.4 JDK/1.6.20 VC#/2005
Library gtk+/2.16.6 Qt/4.3.5 Java/6.0 .NET/2.0
Source Size 19.09 KB 7.28 KB (+10.17 KB) * 13.18 KB 6.28 KB
Binary Size 32.69 KB 34.00 KB 9.48 KB 5.50 KB
Runtime Size 1.46 MB 2.00 MB 15.54 MB 23.84 MB
* Generated source

WPF(Windows Presentation Foundation), WCF(Windows Communication Foundation), WF(Windows Workflow Foundation) 是.NET 3.0 中新加入的类库. 这里会介绍一下WCF 和WF 的大概应用和架构, 关于WPF, 由于丸子也还没能完全得到那个点, 所以可能就 不介绍了, 还需要在多看看砖头.

进入正题, 今天讲的是WCF. 这个东西是用来创建分布式系统的.

首先介绍一下SOA(Service-Oriented Architecture) 的概念. SOA 实际上是一个基于service 的分布式架构. 每个service 对外提供一定的功能接口. 当我们要做一个大的软件的时候, 只要把这些service 提供的接口组合一下就可以了. 那这些service 之间, 或者我们client 程序之间怎么交互呢? 这里就是SOA 实现的问题了. 比如比较流行的有Web Service, Windows 下比较老的DCOM 和MSMQ. 当然还有WCF. Web Service, WCF 代表的是一种SOA的技术或者说实现, 而它们的通信协议(protocol) 是另外一个概念. 比如Web Service 用的可以是SOAP 或是RESTful 协议. WCF 的好处就是, 把以前的一些技术都整合到了一起, 包括DCOM, MSMQ, .NET remoting, Web Service 等.

要建立一个WCF 的应用程序, 基本的组件有三个: a) service assembly, b) service host, c) client. client 端和server 端通信是通过WCF 中的所谓ABC: Address, Binding, Contract. Address 表示的是service 的地址, Binding 表示的是service 的协议, 编码等, Contract 表示的是WCF 对外暴露出的方法.

service assembly 的实现, 主要就是在Contract 这块. 我们用接口(interface) 来定义contract, 然后再用一个具体的类来实现它. 比如:

然后把以上代码编译成一个dll, 就算完成了.

service host 的稍微复杂一些. 主要用到了System.ServiceModel.ServiceHost 这个WCF 中的类来实现application 级的service host. 大概的代码可能是这样:

在代码中, 我们在ServiceHost 类的constructor 中传入了MyService 类型作为所要host 的service. 注意, 要调用这个constructor 的话, MyService 类必须有一个不带参数的constructor, 不然运行时会抛错的那. 当然也可以用一个service 的实例来初始化ServiceHost, 但是比较麻烦, 丸子也没研究过.
以上的代码显然是缺少Address 和Binding 这两快内容的, 我们要在app.config 的配置文件中指定. 配置文件大概是这样的:

好处一串… 先来看<service>这个tag, 它对应的就是contract 的实现类, 用name 属性来指定. 然后是<endpoint>这个tag, 它对应的是单个的contract 的接口. 在运行时, ServiceHost 会进行这样的检查: 根据代码中的MyService 类找到app.config 中对应的<service>的tag, 这个tag 中的name 属性默认就是类名, 但是也可以指定为一个friend name, 不过具体做法未知=v=. 然后分别比对实现类和config 文件中的接口定义是否一致, 如果不一致的话会抛运行时错误. 注意: 我们在所有的<endpoint>中的address 属性都为空, 其实是利用了下面的<baseAddresses>中的baseAddress 属性, 这是一个基地址.

另外还有一个MEX(Metadata Exchange) 的概念, 在config 文件中, 对应一个<endpoint>和<behavior>的tag. 简单来说, 这个东西就是用来生产client 端的代理(proxy) 代码的. 它提供了对于metadata 请求的响应. 当运行这个service host 的时候, 我们可以用browser 来访问mex 这个<endpoint>中的address, 会看到关于这个service 的metadata 信息, 以及如何生产client 端代码的提示. 一旦我们生产了client 端的代码, 如果不希望这些metadata 信息再被发现(discover) 的话, 可以删掉这两个tag.

注意, 如果是vista或者win7 的话, service host 程序必须以管理员权限运行.

最后就是client 端的写法了. client 端怎样通过WCF 的框架来调用远程的service 呢? 我们需要一个proxy 的类, 这个类通过在service host 得到的metadata 信息, 来封装对于service 的远程调用. vs2008 提供了wizard 来实现这个繁冗的过程, 请自由的在项目节点上右键点击add service reference 菜单项. 在出现的对话框中, 会看到service 的列表, 而每个service 包含多个service contract, 而每个service contract 又包含多个operation contract. 我们要生成的proxy 类的代码是service 级别的, 对于每个service, 所有的代码都生成在一个namespace 中. 一旦生产了代码, 我们的client 端代码将是非常简单的:

WCF 还提供了异步调用和自定义数据类型的支持, 这些就请自行查阅MSDN 吧.

今天主要介绍一下LINQ(Language Integrated Query) 在ADO.NET 中的使用, 即LINQ to ADO.NET.

LINQ to ADO.NET 有两个model: LINQ to DataSet 和LINQ to SQL. 我不想讲LINQ 的语法, 主要讲架构.

首先是LINQ to DataSet. 显然, 这套API 就是在DataSet 这套API 上加入了LINQ 的支持. 这个附加的namespace叫做System.Data.DataExtension.dll. 于是我们看到了一个为了LINQ 在.NET 3.5 新加的特性叫做extension method. Extension method 的作用是扩展现有类, 但是不修改源码, 不使用继承或者组合. 只要新加几个函数, 就能让原来的类包含新的方法, 像这些方法本来就被定义了一样. 比如有一个DataTableExtensions 类, 它就在DataTable 类上新加入了AsEnumerable() 方法, 从而把DataTable 类转化成LINQ 兼容的接口类型IEnumerable<T>. 关于extension method 请查阅MSDN.

然后是LINQ to SQL. 这套API 的目的是要提供数据库到代码的一致的映射关系, 也就是说, 我们不要直接操作int, string 等基本数据类型, 而是操作具体的代码层的映射类. 可以跟java 中的hibernate 比对一下, hibernate 里的映射类叫做POJO(Plain Ordinary Java Objects), 而.NET 中则叫做实体类(entity class). 丸子已经很久没写过java 了, java 的东西几乎都忘了. 翻了些网页发现javaee 持久性框架也使用了entiry class 的概念, 跟hibernate 不同的是, 大量使用了annotation, 从而使hibernate 里出现的mapping 文件没有必要使用了. 具体参照: http://salto-db.sourceforge.net/salto-db-generator/plugins/ejb3hibernatedao.html. 而.NET 中与annotation 对应的则是attribute.

要使用LINQ to SQL, 就必须要创建实体类. 那我们用什么对象来访问实体类呢? .NET 中有一个叫做DataContext 的类, 对比java 中hibernate 的话, 可以理解为一个Session 对象. 一般的DataContext 的用法大概是这样:

这里的XXTable 类假设是已经定义的实体类. 这样以后就可以对这个xxTable 对象进行操作了. 更好的方法是定义一个strongly typed 的DataContext 类:

这又是一个非常神奇的类. 假设MyTable1 和MyTable2 都是已定义的实体类, 所有我们所要做的只是简单的把它们定义成public 的field. 每次当我们调用MyDataContext 的时候, 这些table 的field 会自动被填充. 根据reflector 的trace, 大概是用到了reflection 的机制. 之所以要写一个strongly type 的DataContext, 当然是为了不要繁琐的调用GetTable() 这个方法.

不过, strongly type 的DataContext 的最主要的作用, 还是从对entity class 的操作转换为对数据库的操作. 再回忆一下, 为什么要有entity class? 因为LINQ 的需要, 需要一个跟数据库一一映射的代码类. 于是, 所有的东西都联系起来了.

vs2008 提供了wizard 来生产这样的DataContext, 菜单位于project–>add new item–>data–>linq to sql classes. 我们有了这个DataContext 之后, 所有的操作应该都是OO 的了, 也会方便很多.

最后, 从.NET 3.0 开始, assembly 的位置似乎发生了变化. v2.0 的位置在: C:\Windows\Microsoft.NET\Framework, 而v3.0/v3.5 的位置则在: C:\Program Files\Reference Assemblies\Microsoft\Framework.

Disconnected Layer, 在内存中进行把数据库的操作, 然后把这些操作更新回数据库.

这里有一个data adaptor 的概念. 它使用DataSet 类来进行内存中和数据库中数据的移动和更新. Disconnect Layer 只保持最短时间的数据库连接, 所有的数据库操作都是针对内存中的DataSet 的. 所有操作完成后, 再一次完全更新回实际的数据库. 一个DataSet 类中包含三个collection: DataTableCollection, DataRelationCollection 和PropertyRelation, 分别表示所有的table, 所有的relation, 以及这个dataset 中一些附加的属性.

这里又有个东西叫做data-binding. 所谓的数据绑定, 意思就是说把数据(data)和界面(UI) 联系起来, 当数据发生变化的时候, 界面也会做出相应的改变. 好处是节省了很多手动需要refresh 的代码, 坏处确实这个机制会比较慢. 在windows form 中, 我们可以把一个DataTable 类的实例绑定到一个DataGridView 的DataSource 属性上. DataTable 这个类另外支持一个filter criteria 的功能, 其实就是一个查询条件. 这个功能个人觉得比较弱, 没有java 中hibernate 的HQL 的OO 特性, 也不像hibernate 的Criteria 类完全强类型那么好用.

有一个神奇的类DbCommandBuilder. 这个类可以简化与DataTable 类的CRUD (create/read/update/delete) 操作. 已经提过, 在Disconnected Layer 中, 是通过data adaptor 来进行CRUD 操作的. 拿SqlDataAdaptor 这个类来说, 使用前需要指定它的四个Command 属性: SelectCommand, InsertCommand, DeleteCommand, UpdateCommand. 于是有一个SqlCommandBuilder 类, 它可以根据设定的SelectCommand 来生产其它三个Command 对象. 猜想一下, 应该是根据SelectCommand 可以拿到的DB Scheme 来构造其它Command 的, 而事实就是如此. 缺点是使用DbCommandBuilder 有诸多限制, 请查阅MSDN.

最后, 要提到一个DAL(Data Access Library) 的概念, 可以跟java 中DAO 联系一下, 其实就是要封装了一些数据库的操作. 在windows form 的DataGridView 的设计界面上, 我们不但可以用wizard 来绑定数据, 同时还生成了所谓的DAL. 生产的代码就是strong-type 的DataSet 和DataTable 类型. 另外, 即使不使用windows form, vs2008 也提供了生产DAL 的wizard, 菜单位于project–>add new iterm–>data–>dataset. 注意, 这里生产的代码还不是OO 的, 我们操作的依然是基本数据类型, 并没有java 中hibernate 的所谓entiry class 相关概念. 关于entiry class, 会在之后的LINQ 中介绍.

即日起将开始记录.NET 相关的一些笔记, 将会涉及ADO.NET, WPF, WCF, WF. 今天主要是ADO.NET 的内容.

ADO(Active Data Object) 是M$ 的一个数据访问模型, ADO.NET 自然就是这个东西的.NET 升级版. ADO.NET 可以以两种方式使用: 连接的(connected) 和非连接的(disconnected).

Connected Layer, 可以想象成java 里的jdbc 的数据库访问模型. 我们通过一个Connection 对象连接到数据库, 然后用这个Connection 对象创建一个Statement 对象用来select/insert/delete/update 数据库, 最后调用这个Statement 的executeQuery()/executeUpdate() 方法来完成实际的数据库操作.

这些东西在.NET 里的对应关系大概是这样: Connection–>Connection, Statement–>Command, ResultSet–>DataReader. 这里有一个data provider 的概念, 一个data provider 定义了一系列的数据类型, 用来针对某个特定的数据库类型. 比如针对SQLServer 和Oracle, 分别有(SqlConnection, OracleConnection 类), (SqlCommand, OracleCommand 类) 等. 从类库中来看的话, 一个data provider 实际对应了一个namespace, 比如SQLServer–>System.Data.SqlClient, Oracle–>System.Data.OracleClient. 而在这些namespace 中, 又分别有所谓的provider factory, 比如SqlClientFactory, OracleClientFactory 类, 这些factory 类都继承DbProviderFactory 这个基类.

也许有些晕了, 丸子sensei 告诉你, 这里就是一个Abstract Factory 的设计模式. 把DbProviderFactory 这个类看出抽象基类, coding 的时候把数据库相关的具体实现类用基类来引用. 我们知道一个具体的factory 实际上就是用来创建数据库相关的具体操作类的. 所以通过abstract factory 的这一层抽象, 我们做到了全部用抽象方法的调用. 但是还有一个问题, 那就是当我们把一个具体的DbProviderFactory 赋给基类来使用, 是要手动改代码的, 有没有一中方法可以随便换data provider 而不用改代码呢? 显然, 就是使用一个叫做DbProviderFactories 的类, 这个类有个方法可以接受string 来返回一个DbProviderFactory 的具体实现类.

其实java 用的也是类似的abstract factory 模式, 你一定用 Class.forName(“xxx.xxx.xxxDriver”).newInstance() 来注册一个driver, 然后再用DriverManager 类的static 方法来返回一个Connection 对象. jdk6.0 中开始包含了jdbc4.0 的新特性, Class.forName() 可以不必调用了.

关于Disconnected Layer, 请期待下文.