RSS
热门关键字:  数据挖掘  数据仓库  人工智能  搜索引擎  数据挖掘导论

第八章 用C#写组件

来源: 作者:unkonwn 时间:2004-12-07 点击:

这一章关于用C#写组件。你学到如何写一个组件,如何编译它,且如何在一个客户程序中使用它。更深入一步是运用名字空间来组织你的应用程序。

数据挖掘研究院

这章由两个主要大节构成:

。你的第一个组件

。使用名字空间工作

 

8.1  你的第一个组件 数据挖掘研究院

到目前为止,在本书中提到的例子都是在同一个应用程序中直接使用一个类。类和它的使用者被包含在同一个执行文件中。现在我们将把类和使用者分离到组件和客户,它们分别位于不同的二进制文件中(可执行文件)。

数据挖掘研究院

尽管你仍然为组件创建一个 DLL,但其步骤与用C++写一个COM组件差别很大。你很少涉及到底层结构。以下小节说明了如何构建一个组件以及客户是如何使用它的:

  数据挖掘研究院

。构建组件

。编译组件 数据挖掘研究院

。创建一个简单的客户应用程序 数据挖掘实验室

 

8.1.1  构建组件

数据挖掘研究院

     因为我是一个使用范例迷,我决定创建一个相关Web的类,以方便你们使用。它返回一个Web网页并储存在一个字符串变量中,以供后来重用。所有这些编写都参考了.NET Framwork的帮助文档。 数据挖掘研究院

类名为RequestWebPage;它有两个构造函数、一个属性和一个方法。属性被命名为URL,且它储存了网页的Web地址,由方法GetContent返回。这个方法为你做了所有的工作(见清单8.1)。

数据挖掘实验室

 

数据挖掘研究院

清单 8.1   用于从Web服务器返回HTML网页的RequestWebPage 类 数据挖掘研究院

  数据挖掘研究院

 1: using System; 数据挖掘实验室

 2: using System.Net;

 3: using System.IO; 数据挖掘研究院

 4: using System.Text;

 5: 数据挖掘研究院

 6: public class RequestWebPage 数据挖掘研究院

 7: { 数据挖掘研究院

 8:  private const int BUFFER_SIZE = 128;

 9:  private string m_strURL;

10:

数据挖掘实验室

11:  public RequestWebPage()

12:  { 数据挖掘研究院

13:  } 数据挖掘研究院

14: 数据挖掘研究院

15:  public RequestWebPage(string strURL) 数据挖掘研究院

16:  { 数据挖掘研究院

17:   m_strURL = strURL;

18:  } 数据挖掘研究院

19: 数据挖掘研究院

20:  public string URL

数据挖掘实验室

21:  {

22:   get { return m_strURL; } 数据挖掘研究院

23:   set { m_strURL = value; } 数据挖掘研究院

24:  } 数据挖掘研究院

25:  public void GetContent(out string strContent) 数据挖掘实验室

26:  { 数据挖掘研究院

27:   // 检查 URL 数据挖掘实验室

28:   if (m_strURL == "")

数据挖掘研究院

29:    throw new ArgumentException("URL must be provided.");

数据挖掘实验室

30: 数据挖掘研究院

31:  WebRequest theRequest = (WebRequest) WebRequestFactory.Create(m_strURL);

32:   WebResponse theResponse = theRequest.GetResponse(); 数据挖掘研究院

33:

34:   // 给回应设置字节缓冲区 数据挖掘实验室

35:   int BytesRead = 0;

数据挖掘研究院

36:   Byte[] Buffer = new Byte[BUFFER_SIZE];

37:

数据挖掘研究院

38:   Stream ResponseStream = theResponse.GetResponseStream(); 数据挖掘研究院

39:   BytesRead = ResponseStream.Read(Buffer, 0, BUFFER_SIZE); 数据挖掘研究院

40:

数据挖掘研究院

41:   //使用 StringBuilder  以加速分配过程

42:   StringBuilder strResponse = new StringBuilder(""); 数据挖掘研究院

43:   while (BytesRead != 0 ) 数据挖掘研究院

44:   { 数据挖掘研究院

45:    strResponse.Append(Encoding.ASCII.GetString(Buffer,0,BytesRead));

数据挖掘研究院

46:    BytesRead = ResponseStream.Read(Buffer, 0, BUFFER_SIZE); 数据挖掘研究院

47:   } 数据挖掘研究院

48: 数据挖掘实验室

49:   // 赋给输出参数 数据挖掘研究院

50:   strContent = strResponse.ToString(); 数据挖掘研究院

51:  } 数据挖掘研究院

52: } 数据挖掘研究院

  数据挖掘实验室

本应该利用无参数构造函数完成工作,但我决定在构造函数中初始化URL,这可能会很有用。当后来决定要改变URL时——为了返回第二个网页,例如,通过URL属性的get和set访问标志使它被公开了。 数据挖掘实验室

有趣的事始于GetContent方法。首先,代码对URL实行十分简单的检查,如果它不适合,就会引发一个ArgumentException 异常。之后,我请求WebRequestFactory ,以创建一个基于传递给它的URL的WebRequest对象。

数据挖掘研究院

因为我不想发送cookies、附加头和询问串等,所以立即访问WebResponse(第32行)。如果你需要请求上述任何的功能,必须在这一行之前实现它们。

数据挖掘研究院

第35和36行初始化一个字节缓冲区,它用于从应答流(response stream)中读数据。暂时忽略StringBuilder 类,只要应答流中仍然有要读的数据,while循环就会简单地重复。最后的读操作将返回零,因此结束了该循环。 数据挖掘研究院

现在我想回到StringBuilder类。为什么用这个类的实例而不是简单地把字节缓冲区合并到一个字符串变量?看下面这个例子:

数据挖掘研究院

strMyString = strMyString + "some more text"; 数据挖掘研究院

这里很清楚,你正在拷贝值。常量 "some more text" 以一个字符串变量类型被加框,且根据加法操作创建了一个新的字符串变量。接着被赋给了 strMyString。有很多次拷贝,是吗? 数据挖掘研究院

但你可能会争论 数据挖掘研究院

strMyString += "some more text"; 数据挖掘研究院

不要炫耀这种行为。对不起,对于C#这是一个错误的答案。其运行完全与所描述的赋值操作相同。 数据挖掘实验室

不涉及该问题的另外的途径是使用StringBuilder类。它利用一个缓冲区进行工作,接着,在没有发生我所描述的拷贝行为的情况下,你进行追加、插入、删除和替换操作。这就是为什么我在类中使用它来合并那些读自缓冲区中的内容。 数据挖掘研究院

该缓冲区把我领进了这个类中最后重要的代码片段——第45行的编码转换。它只不过涉及到我获得请求的字符集。 数据挖掘研究院

最后,当所有的内容被读入且被转换时,我显式地从 StringBuilder请求一个字符串对象并把它赋给了输出变量。一个返回值仍然会导致另外的拷贝操作。 数据挖掘研究院

  数据挖掘研究院

8.1.2  编译组件 数据挖掘研究院

到目前为止,你所做的工作与在正常应用程序的内部编写一个类没有什么区别。所不同的是编译过程。你必须创建一个库而不是一个应用程序: 数据挖掘研究院

csc /r:System.Net.dll /t:library /out:wrq.dll webrequest.cs

编译开关/t:library  告诉C#编译器,要创建一个库而不是搜寻一个静态 Main方法。同样,因为我正在使用 System.Net名字空间,所以必须引用 (/r:)它的库,这个库就是System.Net.dll。

数据挖掘研究院

你的库命名为 wrq.dll,现在它准备用于一个客户应用程序。因为在这章中我仅使用私有组件工作,所以你不必把库拷贝到一个特殊的位置,而是拷贝到客户应用程序目录。

数据挖掘实验室

 

8.1.3  创建一个简单的客户应用程序

数据挖掘研究院

当一个组件被写成且被成功地编译时,你所要做的就是在客户应用程序中使用它。我再次创建了一个简单的命令行应用程序,它返回了我维护的一个开发站点的首页(见清单8.2)。 数据挖掘研究院

 

清单 8.2    用 RequestWebPage 类返回一个简单的网页 数据挖掘研究院

  数据挖掘研究院

 1: using System;

 2: 数据挖掘研究院

 3: class TestWebReq 数据挖掘研究院

 4: {

 5:  public static void Main() 数据挖掘研究院

 6:  { 数据挖掘实验室

 7:   RequestWebPage wrq = new RequestWebPage(); 数据挖掘研究院

 8:   wrq.URL = "http://www.alphasierrapapa.com/iisdev/";

 9: 数据挖掘研究院

10:   string strResult; 数据挖掘研究院

11:   try

12:   { 数据挖掘研究院

13:    wrq.GetContent(out strResult); 数据挖掘研究院

14:   }

15:   catch (Exception e) 数据挖掘研究院

16:   {

数据挖掘研究院

17:    Console.WriteLine(e); 数据挖掘实验室

18:    return; 数据挖掘研究院

19:   } 数据挖掘研究院

20:

数据挖掘研究院

21:   Console.WriteLine(strResult);

22:  }

23: }

注意,我已经在一个try catch语句中包含了对 GetContent的调用。其中的一个原因是GetContent可能引发一个 ArgumentException异常。此外,我在组件内部调用的.NET Framwork类也可以引发异常。因为我不能在类的内部处理这些异常,所以我必须在这里处理它们。 数据挖掘研究院

其余的代码只不过是简单的组件使用——调用标准的构造函数,访问一个属性,并执行一个方法。但等一下:你需要注意何时编译应用程序。一定要告诉编译器,让它引用你的新组件库DLL: 数据挖掘实验室

csc /r:wrq.dll wrclient.cs

现在万事俱备,你可以测试程序了。输出结果会滚屏,而你可以看到应用程序工作。使用了常规的表达式,你也可以增加代码,以解析返回的HTML,并依据你个人的喜好,提取信息。我预想会使用到这个类新版本的SSL(安全套接字层),用于ASP+网页中的在线信用卡验证。

你可能会注意到,没有特殊的using 语句用于你所创建的库。原因是你在组件的源文件中没有定义名字空间。

 

8.2  使用名字空间工作

数据挖掘研究院

    你经常使用到名字空间,例如System 和System.Net。C#利用名字空间来组织程序,而且组织的层次结构特性使一个程序的元素传到另一个程序变得更容易。即使名字空间不用于外部描述,它们也是内部组织应用程序的好办法。

尽管不是强制的,但你总要创建名字空间,以清楚地识别应用程序的层次结构。.NET Framwork会给出构建这种层次结构的良好思想。 数据挖掘研究院

以下的代码片段显示了在C#原文件中简单的名字空间 My.Test(点号表示一个层次结构等级)的声明:

  数据挖掘研究院

namespace My.Test 数据挖掘研究院

{

  //这里的任何东西属于名字空间

} 数据挖掘研究院

  数据挖掘研究院

当你访问名字空间中的一个元素时,也有必要使用名字空间标识符全面地限定它,或者利用using标志把所有的成员引入到你当前的名字空间。本书前面的例子演示了如何应用这些技术。

数据挖掘研究院

在开始使用名字空间之前,只有少数有关存取安全的词。如果你不增加一个特定的存取修饰符,所有的类型将被默认为internal 。当你想从外部访问该类型时,使用 public 。不允许其它的修饰符。 数据挖掘研究院

这是关于名字空间充分的理论。让我们继续实现该理论——以下小节说明了当构建组件应用程序时,如何使用名字空间

。在名字空间中包装类

。在客户应用程序中使用名字空间

数据挖掘研究院

。为名字空间增加多个类

  数据挖掘研究院

8.2.1  在名字空间中包装类 数据挖掘研究院

    既然你知道了名字空间的理论含义,那么让我们在现实生活中实现它吧。在这个以及即将讨论到的例子中,自然选择到的名字空间是Presenting.CSharp。为了不使你厌烦,仅仅是把RequestWebPage包装到Presenting.CSharp中,我决定写一个类,用于 Whois查找(见清单8.3)。

 

清单 8.3   在名字空间中实现 WhoisLookup类 数据挖掘研究院

  数据挖掘研究院

 1: using System; 数据挖掘研究院

 2: using System.Net.Sockets; 数据挖掘研究院

 3: using System.IO;

数据挖掘实验室

 4: using System.Text;

 5: 数据挖掘研究院

 6: namespace Presenting.CSharp

数据挖掘研究院

 7: { 数据挖掘研究院

 8: public class WhoisLookup

数据挖掘研究院

 9: {

数据挖掘研究院

10:  public static bool Query(string strDomain, out string strWhoisInfo)

11:  {

数据挖掘研究院

12:   const int BUFFER_SIZE = 128; 数据挖掘研究院

13:

14:   if ("" == strDomain)

15:    throw new ArgumentException("You must specify a domain name."); 数据挖掘研究院

16: 数据挖掘实验室

17:   TCPClient tcpc = new TCPClient();

18:   strWhoisInfo = "N/A"; 数据挖掘研究院

19:

数据挖掘研究院

20:   // 企图连接 whois  服务器 数据挖掘研究院

21:   if (tcpc.Connect("whois.networksolutions.com", 43) != 0)

数据挖掘研究院

22:     return false;

23:        

24:   // 获取流

25:   Stream s = tcpc.GetStream(); 数据挖掘研究院

26: 数据挖掘研究院

27:   // 发送请求 数据挖掘实验室

28:   strDomain += " ";

29:   Byte[] bDomArr = Encoding.ASCII.GetBytes(strDomain.ToCharArray()); 数据挖掘实验室

30:   s.Write(bDomArr, 0, strDomain.Length);   数据挖掘研究院

31:     

32:   Byte[] Buffer = new Byte[BUFFER_SIZE]; 数据挖掘实验室

33:   StringBuilder strWhoisResponse = new StringBuilder(""); 数据挖掘研究院

34:

35:   int BytesRead = s.Read(Buffer, 0, BUFFER_SIZE); 数据挖掘研究院

36:   while (BytesRead != 0 )

数据挖掘研究院

37:   { 数据挖掘实验室

38:   strWhoisResponse.Append(Encoding.ASCII.GetString(Buffer,0,BytesRead)); 数据挖掘实验室

39:   BytesRead = s.Read(Buffer, 0, BUFFER_SIZE);

数据挖掘研究院

40:   }

41:

数据挖掘研究院

42:   tcpc.Close(); 数据挖掘研究院

43:   strWhoisInfo = strWhoisResponse.ToString();

数据挖掘研究院

44:   return true;

数据挖掘研究院

45:  } 数据挖掘实验室

46: } 数据挖掘研究院

47: } 数据挖掘研究院

  数据挖掘研究院

名字空间在第6行被声明,而且它用第7行和第47行的花括弧括住了WhoisLookup类。要声明自己新的名字空间,实际要做的就是这些。

在WhoisLookup类中当然具有一些有趣代码,特别它说明了使用C#进行socket编程易如反掌。在static Query 方法中经过“非空”的域名检查之后,我实例化了TCPClient类型的一个对象,它用来完成 Whois服务器所占用的43端口上的所有通讯。在第21行建立了服务器连接: 数据挖掘研究院

if (tcpc.Connect("whois.networksolutions.com", 43) != 0) 数据挖掘研究院

因为连接失败是预料到的结果,所以这个方法不能引发一个异常。(你还记住异常处理的“要”和“不要”吗?) 返回非零值是一个错误代码,而返回零则说明连接成功。 数据挖掘实验室

对于 Whois 查找,我必须首先发出一些信息给服务器——我要查找的域名。要完成此项工作,首先获取一个引用到当前TCP连接的双向流(第25行)。接着在域名后附加上一个回车/换行对,以表示查询结束。重新以字节数组打包,向Whois 服务器发送一个请求(第30行)。

数据挖掘实验室

余下的代码和RequestWebPage类极其相似。在该类中,我再次利用一个缓冲区从远程服务器读入应答。当缓冲区完成读入后,连接被断开。返回的应答被转给了调用者。我显式地调用 Close 方法的原因是我不想等待垃圾收集器毁坏连接。连接时间不要过长,以免占用TCP端口这种稀有资源。 数据挖掘研究院

在可以使用.NET 组件中的类之前,你必须把它作为一个库来编译。尽管现在有了一个已定义的名字空间,该编译命令仍然没有变:

csc /r:System.Net.dll /t:library /out:whois.dll whois.cs 数据挖掘实验室

注意,如果你想该库按与C#源文件相同的方法命名,就没有必要规定 /out:开关。规定该开关是一个良好的习惯,因为很多项目不会只由单个源文件组成。如果你规定了多个源文件,该库以名单中的第一个命名。 数据挖掘实验室

8.2.2  在客户应用程序中使用名字空间

数据挖掘研究院

由于你使用了名字空间开发组件,所以客户也要引入名字空间 数据挖掘实验室

using Presenting.CSharp; 数据挖掘研究院

或者给名字空间中的成员使用完全限定名(fully qualified name),例如

数据挖掘研究院

Presenting.CSharp.WhoisLookup.Query(...);

如果你不期望在名字空间中引入的元素之间出现冲突,using  标志( directive)是首选,特别是由于你具有很少的类型时。使用组件的客户程序样本在清单8.4中给出。

  数据挖掘研究院

清单  8.4  测试 WhoisLookup 组件

  数据挖掘实验室

 1: using System;

 2: using Presenting.CSharp;

数据挖掘研究院

 3: 数据挖掘研究院

 4: class TestWhois

数据挖掘研究院

 5: { 数据挖掘研究院

 6:  public static void Main() 数据挖掘研究院

 7:  { 数据挖掘研究院

 8:   string strResult; 数据挖掘实验室

 9:   bool bReturnValue; 数据挖掘研究院

10:

数据挖掘研究院

11:   try 数据挖掘研究院

12:   { 数据挖掘研究院

13:    bReturnValue = WhoisLookup.Query("microsoft.com", out strResult);

14:   } 数据挖掘研究院

15:   catch (Exception e)

16:   { 数据挖掘研究院

17:    Console.WriteLine(e);

数据挖掘研究院

18:    return;

19:   } 数据挖掘研究院

20:   if (bReturnValue)

21:    Console.WriteLine(strResult);

22:   else

23:    Console.WriteLine("Could not obtain information from server.");

24:  } 数据挖掘研究院

25: }

 

数据挖掘研究院

第2行利用using 标志引入了Presenting.CSharp名字空间。现在,我无论什么时候引用WhoisLookup ,都可以忽略名字空间的完全限定名了。 数据挖掘研究院

该程序对 microsoft.com 域进行一次Whois 查找——你也可以用自己的域名代替microsoft.com 。允许命令行参数传递域名,可使客户的用途更广。清单8.5 实现了该功能,但它不能实现适当的异常处理(为了使程序更短)。 数据挖掘研究院

  数据挖掘研究院

清单  8.5  传递命令行参数给Query 方法 数据挖掘研究院

 

 1: using System; 数据挖掘研究院

 2: using Presenting.CSharp;

数据挖掘实验室

 3:

 4: class WhoisShort 数据挖掘研究院

 5: { 数据挖掘研究院

 6:  public static void Main(string[] args)

数据挖掘研究院

 7:  {

 8:   string strResult;

 9:   bool bReturnValue;

10:

11:   bReturnValue = WhoisLookup.Query(args[0], out strResult); 数据挖掘研究院

12:

数据挖掘研究院

13:   if (bReturnValue) 数据挖掘研究院

14:    Console.WriteLine(strResult);

15:   else 数据挖掘研究院

16:    Console.WriteLine("Lookup failed."); 数据挖掘研究院

17:  }

数据挖掘研究院

18: } 数据挖掘研究院

 

你所必须做的就是编译这个应用程序: 数据挖掘研究院

csc /r:whois.dll whoisclnt.cs 数据挖掘实验室

接着可以使用命令行参数执行该应用程序。例如,以 microsoft.com参数执行

数据挖掘研究院

whoisclnt microsoft.com 数据挖掘实验室

当查询运行成功时,就会出现 microsoft.com的注册信息。(清单8.6 显示了输出的简略版本)  这是一个很方便的小程序,通过组件化的途径写成的,花不到一个小时。如果用C++编写,要花多长时间?很幸运,我再也想不起当第一次用C++这样做时,花了多长的时间。 数据挖掘研究院

 

数据挖掘实验室

清单 8.6   有关 microsoft.com (简略) 的Whois 信息

数据挖掘研究院

D:CSharpSamplesNamespace>whoisclient 数据挖掘研究院

... 数据挖掘研究院

 

Registrant: 数据挖掘实验室

Microsoft Corporation (MICROSOFT-DOM)

数据挖掘研究院

  1 microsoft way

  redmond, WA 98052

数据挖掘研究院

  US 数据挖掘研究院

  Domain Name: MICROSOFT.COM 数据挖掘研究院

 

数据挖掘研究院

  Administrative Contact:

   Microsoft Hostmaster (MH37-ORG) msnhst@MICROSOFT.COM 数据挖掘研究院

  Technical Contact, Zone Contact:

数据挖掘实验室

   MSN NOC (MN5-ORG) msnnoc@MICROSOFT.COM

  Billing Contact:

数据挖掘研究院

   Microsoft-Internic Billing Issues (MDB-ORG)     msnbill@MICROSOFT.COM 数据挖掘研究院

 

数据挖掘实验室

  Record last updated on 20-May-2000.

数据挖掘研究院

  Record expires on 03-May-2010.

数据挖掘研究院

  Record created on 02-May-1991. 数据挖掘研究院

  Database last updated on 9-Jun-2000 13:50:52 EDT.

数据挖掘研究院

 

  Domain servers in listed order:

数据挖掘研究院

  数据挖掘研究院

  ATBD.MICROSOFT.COM      131.107.1.7 数据挖掘研究院

  DNS1.MICROSOFT.COM      131.107.1.240 数据挖掘实验室

  DNS4.CP.MSFT.NET       207.46.138.11

数据挖掘研究院

  DNS5.CP.MSFT.NET       207.46.138.12

 

数据挖掘研究院

8.2.3  增加多个类到名字空间 数据挖掘研究院

使WhoisLookup和RequestWebPage 类共存于同一个名字空间是多么的美妙。既然WhoisLookup已是名字空间的一部分,所以你只须使RequestWebPage 类也成为该名字空间的一部分。

数据挖掘研究院

必要的改变很容易被应用。你只需使用名字空间封装RequestWebPage 类就可以了: 数据挖掘研究院

 

数据挖掘研究院

namespace Presenting.CSharp

{

数据挖掘研究院

public class RequestWebPage

数据挖掘研究院

{ 数据挖掘研究院

... 数据挖掘研究院

} 数据挖掘研究院

}

 

尽管两个类包含于两个不同的文件,但在编译后,它们都是相同名字空间的一部分:

数据挖掘研究院

csc /r:System.Net.dll /t:library /out:presenting.csharp.dll whois.cs webrequest.cs

数据挖掘研究院

 

数据挖掘研究院

你不必按照名字空间的名字给DLL命名。然而,这样做会有助你更容易你记住,当编译一个客户应用程序时要引用哪一个库。 数据挖掘研究院

 

数据挖掘研究院

8.3  小结 数据挖掘研究院

    在这一章中,你学到了如何构建一个可以在客户程序中使用的组件。最初,你不必关心名字空间,但后面第二个组件中介绍了该特性。名字空间在内外部均是组织应用程序极佳的办法。

    C#中的组件很容易被创建,而且只要库和应用程序共存于相同的目录,你甚至不必进行特殊的安装。当要创建必须被多个客户使用的类库时,步骤就有所改变——而下一章将会告诉你为什么。 数据挖掘研究院

  数据挖掘研究院

最新评论共有 0 位网友发表了评论
发表评论
评论内容:不能超过250字,需审核,请自觉遵守互联网相关政策法规。
匿名?