体验Visual Studio 2010 CTP

今天弄到了一份Visual Studio 2010 CTP的虚拟机,是微软官方发布的。解压后虚拟机的磁盘大小估计有25GB,需要内存1GB,因此你的机器配置至少要2GB的内存,才能比较流利地体验2010。该虚拟机所使用的操作系统是Windows Server 2008。在完成第一次运行的配置后,崭新的Start Page映入眼帘:

基本界面

现在开始新建一个Console Application。在“New Project”对话框中,与VS2008一样,仍然存在那个Framework选择框,不过默认的版本是4.0。而且默认的项目模板中也增加了Modeling项目以及Windows Installer XML(WiX)项目:

OK,现在我们创建一个C#的Console Application,突然之间发现代码编辑器有了较大的变化。首先,Code Outline功能未被开启(是否2010还支持,我还不知道,因为貌似没有找到选项),其次就是外观效果,比如智能感知的“List Member”框以及“Parameter Info”提示信息,让人感觉更像JBuilder IDE的效果了。

Call Hierarchy

VS2010新带了一个功能:在方法上点右键然后选择“View Call Hierarchy”,就可以看到与该方法相关的调用层次信息:

生成序列图

可以使用VS2010生成序列图。在Code Editor上点右键,选择“Generate Sequence Diagram”,并设置相应的选项,即可生成序列图:

    

由此可见,与其它版本的VS相比,VS2010更加注重将项目开发的重点放在设计上,这有助于提高软件质量和开发效率,进而降低开发成本。而对于开发人员来说,要求就更高了。

Team Explorer

VS2010还增加了Team Explorer,用于与Team Foundation Server相连,以便更好的实现项目成员的交互与合作。

本文仅对VS2010做了一个大概的介绍,由于本人也是刚刚拿到VS2010 CTP,所以也就看了个大概,将看到的效果贴于此,也是为了能够与读者共享。与.NET Framework 4.0以及C# 4.0相关的技术特性,我争取在完成当前C#版本的基础知识介绍后,在后续的帖子中详细讨论。

转载至:http://www.cnblogs.com/daxnet/archive/2008/11/18/1687009.html

发表在 C# | 留下评论

C#基础:事件(一)

前面简要介绍了委托的基本知识,包括委托的概念、匿名方法、Lambda表达式等,现在讲讲与委托相关的另一个概念:事件。

事件由委托定义,因为事件的触发方(或者说发布方)并不知道事件的订阅方会用什么样的函数名称,这个函数名称由订阅方自己决定。假如不这样做,那么事件的订阅方必须公开一个专门用于处理事件的函数给事件触发方,由触发方在事件触发的时候调用这个函数。这样一来,触发方必须知道订阅方的细节,才能有效地触发事件,显然这是不合理的,触发方与订阅方耦合性太大了,不具备通用性。

事实上,事件的触发方只需要确定好事件处理函数的签名即可。也就是说,触发方只需要定义在事件发生时需要传递的参数,而在订阅方,只需要根据这个签名定义一个处理函数,然后将该函数“绑定”到事件列表,就可以通过签名中的参数,对事件做相应的处理。定义函数签名非常简单,就是使用委托。下面我们来简单看一个例子。这个例子模拟一个服务器程序,它有Start和Stop两个操作,分别表示启动和停止服务。在成功启动以及成功停止时,都会触发一个“成功”的事件,并公布事件发生的确切时间。

一、定义函数签名(委托)

其实对于我们的例子,事件处理函数的签名中只需要一个参数,就是事件发生的确切时间。因此在定义委托的时候,只需要定义一个时间(DateTime)类型的参数即可。为了能够让我们的程序看上去更加标准,并且为了后面描述的方便,我们还是将这个参数封装在一个类里,并且该类继承于System.EventArgs类。

view plaincopy to clipboardprint?
  1. public class ServerEventArgs : System.EventArgs   
  2. {  
  3.     #region Public Properties   
  4.     /// <summary>   
  5.     /// 读取或设置服务器事件发生的时间   
  6.     /// </summary>   
  7.     public DateTime FireDateTime { getset; }  
  8.     #endregion  
  9.  
  10.     #region Constructors   
  11.     /// <summary>   
  12.     /// 默认构造函数,使用当前时间作为服务器事件   
  13.     /// 发生的时间   
  14.     /// </summary>   
  15.     public ServerEventArgs()   
  16.     {   
  17.         this.FireDateTime = DateTime.Now;   
  18.     }   
  19.     /// <summary>   
  20.     /// 使用给定的事件作为服务器事件发生的时间并   
  21.     /// 对参数对象进行初始化   
  22.     /// </summary>   
  23.     /// <param name=”fireDateTime”></param>   
  24.     public ServerEventArgs(DateTime fireDateTime)   
  25.     {   
  26.         this.FireDateTime = fireDateTime;   
  27.     }  
  28.     #endregion   
  29. }   

现在来定义这个委托:

view plaincopy to clipboardprint?
  1. public delegate void ServerEventHandler(object sender, ServerEventArgs e);  

委托的第一个参数表示事件将由谁来触发(谁是事件的发布者),而第二个参数则是我们刚刚定义的事件参数,它只有一个属性,就是事件的触发时间。函数签名(委托)已经定义好了,接下来需要对事件进行定义。

二、定义事件

在C#中,使用event关键字定义事件。事件定义的形式是:“<modifier> event <event_handler> name”,其中modifier是大家熟知的访问修饰符,也就是“public”、“protected”等,event_handler是定义了事件处理函数签名的委托(在本例中,也就是上面的ServerEventHandler),name自然就是这个事件的名称了。由此,我们的两个事件(成功启动事件和成功停止事件)可以定义如下:

view plaincopy to clipboardprint?
  1. /// <summary>   
  2. /// 定义一个事件,当服务器正常启动后,触发该事件   
  3. /// </summary>   
  4. public event ServerEventHandler Started;   
  5. /// <summary>   
  6. /// 定义一个事件,当服务器正常结束后,触发该事件   
  7. /// </summary>   
  8. public event ServerEventHandler Stopped;   

三、触发事件

事件当然由其发布者触发。考察服务器“Server”这个对象,启动和停止是其本身应有的操作,因此,启动与停止是否成功,也就只有它自己知道。那么成功启动与成功停止的事件自然由其自身引发。事件的触发可以在对象中的任何地方发生,比如在本例中,我们可以在Start方法最后部分调用Started事件,而在Stop方法的最后部分调用Stopped事件。从扩展性方面考虑,我们还是把事件的触发单独放到一个protected方法中,这样做的好处是,当我们对“Server”进行扩展的时候,我们还可以重写这个protected方法,以便在事件触发之前再进行其它特殊的操作。因此,我们的事件触发部分就实现如下:

view plaincopy to clipboardprint?
  1. protected virtual void DoStarted(object sender, ServerEventArgs e)   
  2. {   
  3.     if (Started != null)   
  4.         Started(sender, e);   
  5. }   
  6.   
  7. protected virtual void DoStopped(object sender, ServerEventArgs e)   
  8. {   
  9.     if (Stopped != null)   
  10.         Stopped(sender, e);   
  11. }   
  12.   
  13. /// <summary>   
  14. /// 执行服务器的启动操作   
  15. /// </summary>   
  16. public void Start()   
  17. {   
  18.     // TODO: 在此启动服务器   
  19.     DoStarted(thisnew ServerEventArgs(DateTime.Now));   
  20. }   
  21. /// <summary>   
  22. /// 执行服务器的停止操作   
  23. /// </summary>   
  24. public void Stop()   
  25. {   
  26.     // TODO: 在此停止服务器   
  27.     DoStopped(thisnew ServerEventArgs(DateTime.Now));   
  28. }   

由此,服务器在成功启动后,就会调用DoStarted方法,进而触发Started事件;服务器的停止操作也与此类似。我们需要注意到DoStarted和DoStopped方法中的条件判断语句,该语句是用来检查Started和Stopped事件列表中是否有订阅,如果有则触发事件。这种做法是很有必要的,因为并非所有的访问者都会去订阅事件。

四、订阅事件

在订阅者内订阅事件,只需要将事件处理函数添加到相应的事件中即可。在C#中使用“+=”运算符将事件处理函数添加到事件,而使用“-=”运算符将事件处理函数从事件中删除。下面的代码初始化了一个服务器,订阅了该服务器的成功启动与成功停止事件,并试图启动和停止服务。我们可以看到,在服务器成功启动和成功停止完成后,系统会输出启动或停止的具体时间。

view plaincopy to clipboardprint?
  1. class Program   
  2. {   
  3.     static void Main(string[] args)   
  4.     {   
  5.         Server server = new Server();   
  6.         // 订阅成功启动的事件   
  7.         server.Started += new Server.ServerEventHandler(server_Started);   
  8.         // 订阅成功停止的事件   
  9.         server.Stopped += new Server.ServerEventHandler(server_Stopped);   
  10.         // 启动服务   
  11.         server.Start();   
  12.         // 休息3秒钟,以模拟服务的处理时间   
  13.         Thread.Sleep(3000);   
  14.         // 停止服务   
  15.         server.Stop();   
  16.     }   
  17.   
  18.     static void server_Stopped(object sender, ServerEventArgs e)   
  19.     {   
  20.         Console.WriteLine(“Server successfully stopped at: {0}”, e.FireDateTime);   
  21.     }   
  22.   
  23.     static void server_Started(object sender, ServerEventArgs e)   
  24.     {   
  25.         Console.WriteLine(“Server successfully started at: {0}”, e.FireDateTime);   
  26.     }   
  27. }   

本文简要介绍了事件的概念、事件的定义、触发以及订阅的相关内容。有关事件的其它内容,比如EventHandler和EventHandler<T>委托、事件处理函数列表、接口内事件的实现等,将在后续的文章中一一介绍。

本文相关演示源代码:点击下载此文件

转载至:http://www.cnblogs.com/daxnet/archive/2008/11/21/1687008.html

发表在 C# | 留下评论

C#基础:事件(二)

上篇文章介绍了C#中事件的基本实现方式,在本文中,将对最常见的事件委托EventHandler和EventHandler<T>做介绍。

事实上,在前面文章的介绍中,已经涉及到了EventHandler和EventHandler<T>。在C# 2.0泛型出现之前,EventHandler对C#中最常见的事件处理函数进行了签名定义,它指代了这样一些函数,这些函数没有返回值,有两个参数,第一个参数的类型是object,而第二个参数的类型是EventArgs。

在引入泛型之后,.NET Framework加入了EventHandler<T>委托,与EventHandler不同的是,其第二个参数的类型由T指定,而且该类型必须由EventArgs继承。这一点从下面的定义与泛型约束可以看出。

view plaincopy to clipboardprint?
  1. [Serializable]   
  2. public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)    
  3. where TEventArgs: EventArgs;  

现在再回过头来看上文中“服务器”的例子,在该例中,我们新定义了一个事件委托“ServerEventHandler”,事实上,在C# 2.0中,我们也可以使用EventHandler<ServerEventArgs>委托,效果是完全相同的,因为“ServerEventHandler”与“EventHandler<ServerEventArgs>”所指代的函数具有相同的签名形式。因此,在定义事件的时候,可以采用下面的形式,这样看上去更为规范:

view plaincopy to clipboardprint?
  1. /// <summary>   
  2. /// 定义一个事件,当服务器正常启动后,触发该事件   
  3. /// </summary>   
  4. public event EventHandler<ServerEventArgs> Started;   
  5. /// <summary>   
  6. /// 定义一个事件,当服务器正常结束后,触发该事件   
  7. /// </summary>   
  8. public event EventHandler<ServerEventArgs> Stopped;   

转载至:http://www.cnblogs.com/daxnet/archive/2008/11/24/1687007.html

发表在 C# | 留下评论

ACF:让控制台程序变得更为简单

ACF(Adaptive Console Framework)是一款.NET下的控制台程序开发框架。控制台程序不是非常简单吗?还需要开发框架干什么?当你回答完下面的问题以后,你就会发现,原来自己也需要一套合理的控制台程序的开发方案。

  • 控制台程序都提供命令行参数以及参数值,是否每次开发的时候,都需要去解析用户给定的参数?
  • 如果控制台程序需要提供多种参数组合,那么要针对每种参数组合开发一套解析机制,是否感觉很头疼?
  • 要针对命令行参数以及参数值给出说明信息,当用户给定的参数或者参数值不正确时,是否需要额外的时间来编写这些说明信息和文档?
  • 我编写的控制台程序是可测试的吗?是否满足测试驱动开发的需求?
  • 我编写的控制台程序是可扩展的吗?是否能够随心所欲地对其所支持的命令行参数进行开发和扩展?

在你遇到上面的问题时,你就会考虑去使用一个开发框架来帮你实现这些技术细节。每个项目管理人员和开发人员都有同样的感受,就是能够尽快地将所有的资源用在业务的处理上,而那些技术活,就交给框架去完成。ACF之所以是一个框架,是因为它为你提供了开发一个控制台程序的基础平台,并且预留了实现接口,你所要做的仅仅是往这些实现接口中填入你的业务处理逻辑。

现在,让我们预览一下ACF能帮你提升效率的强大功能。catool是ACF的一个示例程序,catool的基本功能就是通过用户输入的命令行参数,对给定的两个整数做加减乘除运算。此外,catool还将根据用户的需求打印程序版本以及帮助信息。

1、Logo与描述信息的输出

当你自己定制了一个控制台程序的实现时,你需要指定这个程序的Logo字符串以及描述信息。在运行的过程中,Logo和描述信息将被打印出来。而这个打印的过程是不需要额外编程的。比如:

2、命令行语法的自动生成

你不需要写任何一行代码,就可以获得专业的命令行语法描述。在语法描述中,强制性参数会以“<>”囊括,可选参数会以“[]”囊括,非选项型参数将以p1, p2…的形式表示出来。ACF还会针对多种命令行参数组合自动生成语法描述。

3、命令行参数的无序性

与正则表达式型的命令行参数解析相比,ACF具有命令行参数无序性,这是参数解析中的一道难题,自己编程实现也需要花费不少精力。ACF的命令行参数无序性,使得用户无需关心每个参数之间的先后顺序即可随心所欲地使用控制台程序。

4、参数名称的多样性

针对同一个参数,ACF支持多个名称,比如上面例子中,用户在指定计算方式的时候,可以使用/method,也可以使用/m。为了输入方便,一般会用/m的形式,而当程序被用于批处理文件,并希望有更好的可读性时,就可以使用/method的形式。而作为开发人员,所要做的仅仅是将这两种形式的参数名称用分号分开。

5、帮助界面(Help Screen)的自动生成

除了设置相应的属性,用户无需编写一行代码,即可获得非常详细的帮助界面。在帮助界面里,你将看到:a) 每种命令行参数组合的详细解释;b) 参数名称以及可选性(是否是可选参数);c) 针对每个参数名称的详细描述。

6、可测试性

使用ACF开发的控制台程序具有可测试性(testable)。这对测试驱动开发是个很大的帮助。这是因为,每种命令行参数的业务处理逻辑都将单独实现,开发人员能够很方便地将这些逻辑放在独立的.NET程序集中(甚至于分布在多个程序集中)。针对每种命令行参数,开发人员都能很方便地编写测试用例,对其业务处理逻辑进行测试。

7、可扩展性

最新版本(3.5.3253.15384及其以上版本)将支持多元的“选项契约(Option Contract)仓储”(在ACF中,每种命令行参数的组合或者说每种命令行语法都与一个“选项契约”相对应,包含有多个选项契约的程序集称为“选项契约仓储”)。这就使得控制台程序所能支持的命令行语法能够随意扩展:

  • 添加新的语法规则:开发人员只需要编写新的“契约仓储”,并将其名称加入配置文件即可
  • 删除已有语法规则:开发人员只需要修改配置文件即可

如上讲了ACF的七大特色,在使用的过程中,你将发现ACF的确能够很大程度地提高你的开发效率。ACF也有成功的案例:来自加拿大的Bil Simser(Microsoft MVP,MSDN Canada Speakers Bureau成员)在其开发的Tree Surgeon开源项目中就使用了ACF,他还写了一篇博客专门介绍ACF的使用过程与心得,点击这里可以看到他写的这篇文章。

 

你可以到CodePlex站点的ACF主页上获得最新的源代码以及安装包。在使用安装包时,安装程序会自动将所需的组件添加到你的“Add Reference(添加引用)”对话框中,并且会将使用说明以及开发文档安装到开始菜单。

 

ACF主页:www.codeplex.com/acf

ACF源代码地址:http://www.codeplex.com/acf/SourceControl/ListDownloadableCommits.aspx

ACF安装包下载地址:http://www.codeplex.com/acf/Release/ProjectReleases.aspx?ReleaseId=17617

转载至:http://www.cnblogs.com/daxnet/archive/2008/11/27/1687006.html

发表在 C# | 留下评论

C#基础:事件(三)

在讨论事件订阅之前,我们先来看看委托的另一个特性,即调用列表(invocation list)。对于某个特定的委托而言,我们既可以将其它的委托加入其调用列表中,也可以从其调用列表中将其它的委托移除。那么当程序使用这个委托的时候,就会循环遍历并执行其调用列表中的所有委托。

下面请看一个例子,在这个例子中,我们定义了一个委托Callback,并将指代两个函数Func1和Func2的委托依次加入其调用列表中。在调用委托的时候,我们发现,Func1和Func2被依次调用。

view plaincopy to clipboardprint?
  1. class Program   
  2. {   
  3.     delegate void Callback();   
  4.   
  5.     static void Func1()   
  6.     {   
  7.         Console.WriteLine(“Hello”);   
  8.     }   
  9.   
  10.     static void Func2()   
  11.     {   
  12.         Thread.Sleep(3000);   
  13.         Console.WriteLine(“World”);   
  14.     }   
  15.   
  16.     static void Main(string[] args)   
  17.     {   
  18.         Callback cb = null;   
  19.         cb = (Callback)Callback.Combine(cb, new Callback(Func1));   
  20.         cb = (Callback)Callback.Combine(cb, new Callback(Func2));   
  21.         cb();   
  22.     }   
  23. }   

上面的代码会依次输出“Hello”和“World”。细心的读者已经发现,在输出“Hello”之后程序停顿了3秒钟,也就是说,委托的调用列表是顺序同步执行的,其执行顺序与调用列表中委托的加入顺序相同。

现在我们的讨论回到事件上来。我们看看,在C#中,事件的本质究竟是什么。在使用reflector对我们开发的EventDemo例子进行反编译后,我们可以看到,在Started和Stopped事件节点下多出了两个方法:add_xxx和remove_xxx(xxx是事件的名称)。在reflector右边的窗口中,还能看到这两个方法的源代码。

view plaincopy to clipboardprint?
  1. [MethodImpl(MethodImplOptions.Synchronized)]   
  2. public void add_Started(ServerEventHandler value)   
  3. {   
  4.     this.Started = (ServerEventHandler) Delegate.Combine(this.Started, value);   
  5. }   
view plaincopy to clipboardprint?
  1. [MethodImpl(MethodImplOptions.Synchronized)]   
  2. public void remove_Started(ServerEventHandler value)   
  3. {   
  4.     this.Started = (ServerEventHandler) Delegate.Remove(this.Started, value);   
  5. }   

原来,事件在C#中会被编译器翻译成两个方法(add_xxx和remove_xxx),在这两个方法中,会使用Delegate的Combine和Remove方法来将给定的事件处理委托添加到调用列表中。那么,当多个对象对事件进行订阅后,一旦事件触发,那么调用列表中的委托也将依次执行。注意每个方法上面的MethodImpl特性,该特性是用来保证线程安全的,防止多个线程在同一时刻访问调用列表而出现冲突(该话题今后讨论)。

为了使用的方便,C#使用“+=”和“-=”运算符实现事件订阅,事实上也就是在调用add_xxx和remove_xxx方法。这个过程由编译器自动解析并处理。请看下面的IL代码:

view plaincopy to clipboardprint?
  1. IL_0014:  callvirt   instance void [EventDemo.Lib]EventDemo.Lib.Server::add_Started(class [EventDemo.Lib]EventDemo.Lib.Server/ServerEventHandler)   

这行代码就是EventDemo示例中,Main函数订阅server实例Started事件的IL代码。明显看不到“+=”运算,取而代之的是对add_Started方法的调用。

本文简单的剖析了C#中的事件模型。事实上对于委托是如何处理调用列表这一问题,我们也可以通过reflector或者ildasm.exe来继续深究,其本身应该就是维护一个委托的数组。在下文中,将会对事件的最后一个话题:add和remove关键字进行讨论。

转载至:http://www.cnblogs.com/daxnet/archive/2008/12/01/1687005.html

发表在 C# | 留下评论

学习C#和.NET的资源

在此把平时经常去的学习C#和.NET的地址链接整理一下,当然也还包括其它不错的站点,也会列举在此,以作必要时参考(不断更新中)。

  1. CodeProject:http://www.codeproject.com,不仅仅是学习C#/.NET,它还包括了诸如C++、COM等其它技术的内容
  2. CodePlex:http://www.codeplex.com,MS支持的开源网站,有N多源代码可供下载或参考。本人写的Adaptive Console FrameworkStoreDDD以及gulu都发布在这个站点上
  3. CSharp Corner:http://www.c-sharpcorner.com,一个不错的C#学习角,包括资源下载、论坛以及技术博客等
  4. asp.net:http://www.asp.net,Microsoft ASP.NET官方网站。以asp.net的学习资源、论坛为主
  5. InstallShield官方论坛:http://community.acresso.com,基本上与InstallShield相关的问题,都可以在此找到答案
  6. Bytes.com:http://bytes.com,一个汇集了IT行业专家的论坛网站,在这里,各种各样的问题都会有专业的解答
  7. Microsoft Dynamics AX Community:https://community.dynamics.com/ax/home.aspx,微软Dynamics AX技术社区

转载至:http://www.cnblogs.com/daxnet/archive/2008/12/02/1687003.html

发表在 C# | 留下评论

C#基础:事件(四)

事件定义的时候,可以使用add和remove关键字来自定义事件处理函数的添加与移除功能。例如,可以在添加和移除之前,使用lock关键字实现线程同步。虽然MethodImplAttribute会用当前类的对象作为同步对象实现线程同步,但当对象需要向外界公布多个事件的时候,这样做会产生效率问题。比如:对象A向外界公布了E1、E2两个事件,订阅方O1使用+=运算符试图订阅E1事件;订阅方O2也使用+=运算符试图订阅E2事件。假设这两个订阅操作同时进行,那么无论谁先抢到订阅权,另一个操作不得不等待,直到前一个订阅操作成功完成。这是因为,MethodImplAttribute会将A用作线程同步的锁定对象;对于O1和O2而言,在订阅事件的时候,是共用同一个锁定对象的。MethodImplAttribute另一个问题在于,如果A是一个值对象,那么就根本没法使用A作为锁定对象,因为A根本没有“同步索引”,因此你就无法使用多线程去同步使用这样的对象,即使是使用了MethodImplAttribute,也只不过是一个摆设。

仍然以EventDemo项目为例,我们将该案例中Server类的事件定义部分稍作改动,将其改为下面的形式:

view plaincopy to clipboardprint?
  1. private readonly object syncRoot_Started = new object();   
  2. private readonly object syncRoot_Stopped = new object();   
  3.   
  4. private ServerEventHandler m_StartedEventHandler;   
  5. private ServerEventHandler m_StoppedEventHandler;   
  6.   
  7. /// <summary>   
  8. /// 定义一个事件,当服务器正常启动后,触发该事件   
  9. /// </summary>   
  10. public event ServerEventHandler Started   
  11. {   
  12.     add   
  13.     {   
  14.         lock (syncRoot_Started)   
  15.         {   
  16.             m_StartedEventHandler += value;   
  17.         }   
  18.     }   
  19.     remove   
  20.     {   
  21.         lock (syncRoot_Started)   
  22.         {   
  23.             m_StartedEventHandler -= value;   
  24.         }   
  25.     }   
  26. }   
  27.   
  28. /// <summary>   
  29. /// 定义一个事件,当服务器正常结束后,触发该事件   
  30. /// </summary>   
  31. public event ServerEventHandler Stopped   
  32. {   
  33.     add   
  34.     {   
  35.         lock (syncRoot_Stopped)   
  36.         {   
  37.             m_StoppedEventHandler += value;   
  38.         }   
  39.     }   
  40.     remove   
  41.     {   
  42.         lock (syncRoot_Stopped)   
  43.         {   
  44.             m_StoppedEventHandler -= value;   
  45.         }   
  46.     }   
  47. }   
  48.   
  49. protected virtual void DoStarted(object sender, ServerEventArgs e)   
  50. {   
  51.     if (m_StartedEventHandler != null)   
  52.         m_StartedEventHandler(sender, e);   
  53. }   
  54.   
  55. protected virtual void DoStopped(object sender, ServerEventArgs e)   
  56. {   
  57.     if (m_StoppedEventHandler != null)   
  58.         m_StoppedEventHandler(sender, e);   
  59. }   

现在,我们新加入了用于同步的对象syncRoot_Started和syncRoot_Stopped,它们被定义为Server的私有只读成员;在定义事件处理列表添加与移除的逻辑里,使用lock关键字实现线程同步,确保对于同一个事件的调用列表,在同一时刻只有一个线程对其进行操作。在前面的事件实现过程中,由于我们使用默认的add和remove方法,因此C#编译器会自动生成一个类似于上述代码中m_StartedEventHandler、m_StoppedEventHandler的私有成员,而在自定义的实现方式里,开发人员必须手工添加这样的私有成员。

C#中的属性可以是只包含get的只读属性,可以是只包含set的只写属性,还可以是既包含get又包含set的读写属性;而event的定义不同,add和remove必须成对出现。

还有一种情况下,会用add和remove来自定义事件的处理过程的添加与移除,就是当某个对象需要向外界公布多个事件时,此时,没有必要针对每个事件都定义一个私有成员,具体做法是,在类中定义一个集合(比如字典),在add中,向集合添加事件处理过程,而在remove中,将事件处理过程从集合中移除。我们再次改造上述实例,通过使用System.ComponentModel.EventHandlerList类来实现这样的效果:

view plaincopy to clipboardprint?
  1. private readonly object syncRoot_Started = new object();   
  2. private readonly object syncRoot_Stopped = new object();   
  3.   
  4. //private ServerEventHandler m_StartedEventHandler;   
  5. //private ServerEventHandler m_StoppedEventHandler;   
  6. private readonly object eventStarted = new object();   
  7. private readonly object eventStopped = new object();   
  8.   
  9. private EventHandlerList eventHandlerList = new EventHandlerList();   
  10.   
  11. /// <summary>   
  12. /// 定义一个事件,当服务器正常启动后,触发该事件   
  13. /// </summary>   
  14. public event ServerEventHandler Started   
  15. {   
  16.     add   
  17.     {   
  18.         lock (syncRoot_Started)   
  19.         {   
  20.             // m_StartedEventHandler += value;   
  21.             eventHandlerList.AddHandler(eventStarted, value);   
  22.         }   
  23.     }   
  24.     remove   
  25.     {   
  26.         lock (syncRoot_Started)   
  27.         {   
  28.             // m_StartedEventHandler -= value;   
  29.             eventHandlerList.RemoveHandler(eventStarted, value);   
  30.         }   
  31.     }   
  32. }   
  33.   
  34. /// <summary>   
  35. /// 定义一个事件,当服务器正常结束后,触发该事件   
  36. /// </summary>   
  37. public event ServerEventHandler Stopped   
  38. {   
  39.     add   
  40.     {   
  41.         lock (syncRoot_Stopped)   
  42.         {   
  43.             // m_StoppedEventHandler += value;   
  44.             eventHandlerList.AddHandler(eventStopped, value);   
  45.         }   
  46.     }   
  47.     remove   
  48.     {   
  49.         lock (syncRoot_Stopped)   
  50.         {   
  51.             // m_StoppedEventHandler -= value;   
  52.             eventHandlerList.RemoveHandler(eventStopped, value);   
  53.         }   
  54.     }   
  55. }   
  56.   
  57. protected virtual void DoStarted(object sender, ServerEventArgs e)   
  58. {   
  59.     ServerEventHandler startedHandler = (ServerEventHandler) eventHandlerList[eventStarted];   
  60.     if (startedHandler != null)   
  61.         startedHandler(sender, e);   
  62. }   
  63.   
  64. protected virtual void DoStopped(object sender, ServerEventArgs e)   
  65. {   
  66.     ServerEventHandler stoppedHandler = (ServerEventHandler) eventHandlerList[eventStopped];   
  67.     if (stoppedHandler != null)   
  68.         stoppedHandler(sender, e);   
  69. }   
  70.  

转载至:http://www.cnblogs.com/daxnet/archive/2008/12/11/1687002.html

发表在 C# | 留下评论

在Windows Live Writer中使用语法高亮插件

原本我一直是使用Windows Live Writer(WLW)在自己的英文博客上发布文章,在将博客换成中文的PJBLOG后,我也就没有再使用WLW了。今天我重新打开了WLW,尝试着配置以便能够支持PJBLOG。结果发现,WLW确实能够很好的兼容PJBLOG,而且图片的发布也非常方便,这对于广大博友来说,无非是一个很好的消息,WLW也能够满足广大博友的写博需求。然而,对于我这个时不时会在博客中加入程序源代码的软件人员来说,WLW少了一个非常重要的功能,就是以语法高亮的形式插入源代码。

 

无奈,只好baidu一下,发现在codeplex上的确有一款还不错的WLW插件,其地址是:http://www.codeplex.com/Highlight4Writer。哎,天天在codeplex上混,居然还不知道这个插件的存在。仔细查看后发现,原来这款插件最后更新是在2006年,已经很久没有更新了。不管它,反正能用就行了。于是下载了下来,将所有的文件解压到WLW的安装目录下的Plugins目录(在我的机器上,是“C:\Program Files\Windows Live\Writer\Plugins”目录),重新启动WLW,就会在“插入”菜单下出现一个“Syntax highlighted text…”选项,如下:

image

单击这个选项,出现一个对话框,在其中输入你要插入的程序代码,选择程序语言,然后单击“Insert”按钮即可:

image

插入的代码效果如下:

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
		Console.WriteLine("Hello World!");
        }
    }
}

总体还是不错的,虽然感觉行间距有点大,但至少已经可以在WLW中实现语法高亮了。

转载至:http://www.cnblogs.com/daxnet/archive/2008/12/15/1687001.html

发表在 C# | 留下评论

Adaptive Console Framework 最新版发布

经过一个多月的努力,用于在.NET下开发控制台程序的框架ACF终于有了新的版本。ACF简化了.NET下具有复杂命令行语法的控制台程序的开发过程,不仅使得控制台程序具备可测试性,而且开发人员还能够非常方便地向控制台程序发布新的命令行语法。

本次版本更新涉及到了下面几个方面:

  • 性能优化
  • 支持多个“契约仓储(Contract Repository)”,以使得控制台程序有着良好的扩展性
  • 支持使用模板来定制帮助界面的显示内容
  • 添加了《开发者手册(Developer’s Manual)》
  • 采用InstallShield 2009制作安装程序,更新了安装程序的用户体验
  • BUG修正

下面的FLASH录像教您如何在30分钟之内实现一个具有多命令行语法的控制台程序。

 

请单击这里以全屏的方式观看该录像。

请单击这里下载此版本的ACF(版本号:3.5.3253.15384),下载前需要同意LGPL许可,单击“I Agree”就可以进入下载。要查看ACF相关的文档,请在安装完成后,到“开始”->“所有程序”->“SunnyChen.ORG”->“Adaptive Console Framework”中查看。

转载至:http://www.cnblogs.com/daxnet/archive/2008/12/19/1687000.html

发表在 C# | 留下评论

AppDomain与Assembly的加载与卸载

为了将问题描述清楚,我们先来看一个例子。在这个例子中,WinForm上有一个按钮,当用户点击这个按钮后,就会装载一个已经存在的Assembly,并且在界面的Label控件上显示出这个Assembly的FullName。对Reflection稍微熟悉一点的朋友都知道,这是非常简单的事情,只需要用Assembly.LoadFile方法获得Assembly,然后用FullName属性来显示即可,比如下面的代码:

  1. private void button1_Click(object sender, EventArgs e)  
  2. {  
  3.     Assembly assembly = Assembly.LoadFile(@“C:\testlib.dll”);  
  4.     label1.Text = assembly.FullName;  
  5. }  

当然,程序执行正常,您不会发现任何编译时或运行时的错误。然而,当你在没有退出此程序之前,再去编译被调用的testlib.dll,你会发现,Visual Studio无法完成编译,提示说该文件正在被其它的进程所使用,如下:

事实上,我们的程序与这个testlib.dll并没有太大的关联,我们的程序只不过就是显示一下testlib.dll的基本信息。如果testlib.dll是一个共享的库,那么资源独占问题会影响到其它程序的正常工作。

Assembly没有Unload的功能,但可以使用AppDomain来解决这个问题。基本思路是,创建一个新的AppDomain,在这个新建的AppDomain中装载assembly,调用其中的方法,然后将获得的结果返回。在完成所有操作以后,调用AppDomain.Unload方法卸载这个新建的AppDomain,这样也同时卸载了assembly。注意:你无法将装载的assembly直接返回到当前应用程序域(AppDomain)。

首先,创建一个RemoteLoader,这个RemoteLoader用于在新建的AppDomain中装载assembly,并向外公布一个属性,以便外界能够获得assembly的FullName。RemoteLoader需要继承于MarshalByRefObject。代码如下:

  1. public class RemoteLoader : MarshalByRefObject  
  2. {  
  3.     private Assembly assembly;  
  4.   
  5.     public void LoadAssembly(string fullName)  
  6.     {  
  7.         assembly = Assembly.LoadFrom(fullName);  
  8.     }  
  9.   
  10.     public string FullName  
  11.     {  
  12.         get { return assembly.FullName; }  
  13.     }  
  14. }  

其次,创建一个LocalLoader。LocalLoader的功能是创建新的AppDomain,然后在这个新的AppDomain中调用RemoteLoader,以便通过RemoteLoader来创建assembly并获得assembly的相关信息。此时被调用的assembly自然被装载于新的AppDomain中。最后,LocalLoader还需要提供一个新的方法,就是AppDomain的卸载。代码如下:

  1. public class LocalLoader  
  2. {  
  3.     private AppDomain appDomain;  
  4.     private RemoteLoader remoteLoader;  
  5.   
  6.     public LocalLoader()  
  7.     {  
  8.         AppDomainSetup setup = new AppDomainSetup();  
  9.         setup.ApplicationName = “Test”;  
  10.         setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;  
  11.         setup.PrivateBinPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, “private”);  
  12.         setup.CachePath = setup.ApplicationBase;  
  13.         setup.ShadowCopyFiles = “true”;  
  14.         setup.ShadowCopyDirectories = setup.ApplicationBase;  
  15.   
  16.         appDomain = AppDomain.CreateDomain(“TestDomain”null, setup);  
  17.         string name = Assembly.GetExecutingAssembly().GetName().FullName;  
  18.         remoteLoader = (RemoteLoader)appDomain.CreateInstanceAndUnwrap(  
  19.             name,  
  20.             typeof(RemoteLoader).FullName);  
  21.     }  
  22.   
  23.     public void LoadAssembly(string fullName)  
  24.     {  
  25.         remoteLoader.LoadAssembly(fullName);  
  26.     }  
  27.   
  28.     public void Unload()  
  29.     {  
  30.         AppDomain.Unload(appDomain);  
  31.         appDomain = null;  
  32.     }  
  33.   
  34.     public string FullName  
  35.     {  
  36.         get  
  37.         {  
  38.             return remoteLoader.FullName;  
  39.         }  
  40.     }  
  41. }  

最后,修改我们WinForm上的Button Click事件处理过程,改为如下的形式:

  1. private void button1_Click(object sender, EventArgs e)  
  2. {  
  3.     LocalLoader ll = new LocalLoader();  
  4.     ll.LoadAssembly(@“C:\testlib.dll”);  
  5.     label1.Text = ll.FullName;  
  6.     ll.Unload();  
  7. }  

在完成上述的修改后,我们的程序也同样能够正确地显示assembly的FullName,而且,在显示完assembly信息后,程序会主动卸载新建的AppDomain,以防止testlib.dll的资源独占,影响其它程序的运行。

转载至:http://www.cnblogs.com/daxnet/archive/2009/01/12/1686999.html

发表在 C# | 留下评论

C#基础:接口(二)

上文已经提到接口的基本知识,以及如何从面向对象的角度去认知接口与抽象类的联系和区别。本文将从另外的一些技术角度来继续讨论接口和抽象类的取舍问题。

 

首先,在C#中,一个类仅可以继承于一个基类(可以是抽象类,也可以是普通的非密封类),但是它可以实现多个接口。因此,当某个类需要同时拥有多种不同的操作时,封装这些操作的类型就不得不被定义为接口。例如,“学生”实体本身可以根据年龄来比较大小,同时它作为一种实体,还需要能够被序列化/反序列化。此时,我们可以让学生实体同时实现ISerializable和IComparable接口以达到这样的效果。严格的来说,并非所有实体都具有可比性,但从技术角度来讲,实体都应该具备序列化/反序列化的能力。因此在这里的案例中,我们应该定义一个IEntity的接口,使其实现ISerializable接口,然后再让学生实体同时实现IEntity和IComparable接口。

 

其次,基类可以定义更多的信息,当相似种类的对象有着共同的操作和/或属性时,基类可以提供一个默认值,而子类则可以根据需要来选择是否重载/重写这些默认值。这一点对于接口来说是无法办到的。比如上文中我们给电池的正负极一个默认值,直到最后定义“标准镍氢电池”时,我们仍然没有去重载这个值,而是一直沿用了定义在基类中的默认值,这在代码重用上有着重要的意义。不仅如此,当我们修改了基类中某个成员的默认值时,所有继承于该类的子类,如果没有重载这个成员的话,那么它们中相应成员的取值也会随之变化,而这一变化并不需要对子类进行重新编译。

 

再次,由于值类型都是继承于ValueType,因此,无法再让值类型继承于其它类型,但值类型可以实现多个接口。当某一操作集合需要应用在值类型上时,只能将其封装到接口。

 

至此,基本上对抽象类与接口的使用做了全面的讨论,事实上也并没有一个严格的标准去区分什么时候用抽象类、什么时候用接口。我想,最关键也是最主要的依据仍然是前面一篇文章中所提到的,使用面向对象的语义去区分,而这就需要长期不断的经验积累,才能做出更加准确的理解与判断。

转载至:http://www.cnblogs.com/daxnet/archive/2009/02/06/1686997.html

发表在 C# | 留下评论

Windows Live Writer插件:在WLW中插入计算结果

    在一个星期不到的时间里,空闲之余仍然摆弄着Visual Studio 2008,反正一直都在感叹Visual Studio的强大功能,也一直在考虑一些有关用.NET构造大型企业级应用的基础问题。今天,暂且不提什么大型企业级应用,这也不是短时间能够思考清楚的,先拿Visual Studio 2008做些小东西,为Windows Live Writer开发一个插入计算结果的插件吧。

    在我们平时写博客的时候,或许会有这样的情况:摆事实,列出了一大堆数字,最后用计算后的结果给出一个结论。通常的做法是,打开计算器或者Excel,将这些数字输入进去并进行计算,最后将结果复制/粘贴到博客输入框里。既然Windows Live Writer支持插件,为何不自己开发一个插件来实现这样的功能呢?

    出于这种简单的需要,我自己做了一个插件,从“插入”菜单里看,它的名字叫”Evaluated value”,旁边还有一个计算器形状的图标,如下图:

image

点击这个菜单,出现一个对话框:

image

    在这个对话框中,你可以选择表达式的输入方式,也可以选择是以整数输出,还是以小数输出。如果你选择的是小数,Windows Live Writer还允许你选择输出的小数位数(多余部分将以四舍五入的形式截断)。对于表达式的输入方式,分为“简单表达式”和“计算过程”两种,简单表达式就是最普通的加减乘除四则运算表达式,当然你可以在你的表达式中调用.NET System命名空间里的类或者函数。比如:当你输入(23.5+34)*Math.Sin(0.3*Math.PI)并点击确定后,就会在你的博客输入框的光标处插入”46.518″。

    在“计算过程”的输入方式中,你可以输入以C#为语法的脚本语句,当获得了计算结果后,只需将计算结果赋值给result即可。请看下面的截图:

image

    此时,当你点击“确定”按钮后,就会在光标处插入”-25″。

    本插件使用了SyntaxBox的控件,用于在“计算过程”输入方式中提供语法高亮。该控件是开源的,其主页是:http://www.codeplex.com/SyntaxBox

    你可以单击 这里 下载本插件,下载完成后,将压缩包解压到<Windows Live Writer安装目录>\Plugins目录下,再重启Windows Live Writer即可。在默认安装Windows Live Writer的情况下,“Windows Live Writer安装目录”就是C:\Program Files\Windows Live\Writer。

转载至:http://www.cnblogs.com/daxnet/archive/2009/02/12/1686992.html

发表在 C# | 留下评论

Windows Live Writer插件:在WLW中插入语法高亮代码

    前段时间在网上看到了一款在WLW中插入语法高亮代码的插件,叫做Highlight4Writer,试用了一下,能够完成基本功能,但有一些不尽人意的缺点:1、插入的代码行间距太大,看起来不舒服,一页只能显示少量代码;2、支持的程序语言有限,无法自己定制;3、语言语法高亮颜色无法自己定制;4、无法显示行号。针对这些问题,我借用SyntaxBox的支持,自己开发了一款在WLW中插入语法高亮代码的插件。

    下载并将压缩包解压到WLW的Plugins目录后,重新启动Windows Live Writer,就会在“插入”菜单中多出一个名叫Syntax highlighted code的菜单项,该菜单项左边有个类似C#代码文件的图标,如下图所示:

 

    在此说明一下,我现在拿来演示的Windows Live Writer是英文版的,所以您在此看到的所有界面上的内容都是英文的。您无需为自己不懂英语而感到担心,本站所开发的基于Windows Live Writer的插件都是多语言的,目前支持中文和英文两种。上文中提到的在WLW中插入计算结果的插件也是同时支持中文与英文的。关键取决于您的Windows Live Writer所支持的语言。

    OK,现在单击这个菜单项,会出现下面的界面:

    在Language(语言)下拉单中您可以选择多达36种程序设计语言进行代码高亮着色;在Line height(行间距)中您可以设置代码行与行之间的距离,以点为单位;在Show line numbers(显示行号)上,通过勾选来确定是否还要输出行号。在输入框输入了代码后,点击OK(确定)按钮即可插入代码。下面就是使用本插件插入的代码示例,怎么样?还不错吧?

★ Microsoft Dynamics AX X++代码:

    怎么样?总体感觉还是不错吧。可能插入以后,格式上还需要做稍许微调,这就需要根据博文的布局来了。

    您可以单击 这里 下载本插件,下载完成后,将压缩包解压到WLW的Plugins目录下即可。上文中在WLW中插入计算结果的插件也使用到了SyntaxBox,因此,如果你已经安装了该插件,那么在解压的时候会提示你是否覆盖Puzzle.SyntaxBox.NET3.5.dll文件。出于输出HTML的需要,我已经对SyntaxBox的源代码做了修改,因此,不管怎么样,在此选择“覆盖”即可。

转载至:http://www.cnblogs.com/daxnet/archive/2009/02/14/1686991.html

发表在 C# | 留下评论

【领域驱动设计】.NET实践:前言

    从本文开始,将会有一系列的文章介绍领域驱动设计在.NET中的实践,并探讨在.NET环境中实践领域驱动设计的最佳操作。领域驱动设计是一种新兴的软件设计思想,它不是理论,不是圣经,而是前辈多年来实践经验的总结。与模型驱动相比,领域驱动更关注领域,在架构大型软件系统方面,领域驱动设计更具有指导意义。

     Eric Evans在他所写的《领域驱动设计:软件核心复杂性应对之道》一书中,详细地介绍了与领域驱动设计相关的概念与实践经验。书中内容均为作者在其多年大型系统架构和开发过程中的经验总结,因此他没有用“为了达到什么什么目的,我们应该怎么怎么样”这样的语气去决定一件事情。

     目前互联网上有关领域驱动设计的资源也有很多,下面罗列了一些比较好的资源链接:

     由于在.NET环境中应用领域驱动设计的实践经验的相关介绍比较少,因此本系列文章将逐渐讨论与.NET相关的软件设计实践话题,当我们在做设计的时候,会或多或少的存在一些疑问,这些疑问可能是理论性的,也可能是与.NET相关的,可能是领域驱动设计方面的,也有可能是其它思维形态方面的,我也会尽量在本系列文章中对这些问题展开讨论。因此,或许你会看到,在某些篇幅中,领域驱动设计的内容不会太多。文章不会再对领域驱动设计的具体概念细节做重复介绍,请对领域驱动设计不了解的朋友,先通过上面提供的链接对领域驱动设计的基本概念做一些了解。

     最后说明一下,软件设计应需而变,没有对与错,只有合理不合理,本系列文章也是我的一些正式或非正式的经验总结,自然会有很多不足的地方,这还得请前辈们海涵为是。我很希望我所写的这些内容能够帮助到更多的人。为了描述方便,在后续的文章中,“领域驱动设计(Domain Driven Design)”一词将以”DDD”代替。

转载至:http://www.cnblogs.com/daxnet/archive/2009/03/05/1686989.html

发表在 C# | 留下评论

【领域驱动设计】.NET实践:从需求开始

    在软件系统的整个开发过程中,需求分析是非常重要的一个环节,这一点大家都知道,这句话估计很多人都能脱口而出;然而在实际应用中,却往往容易被人忽视。为什么会出现这种情况呢?我想这也是可以理解的。理论毕竟是理论,与实际应用会有所偏差。比如一些外包项目,由于时间的紧迫,团队必须在较短的时间里做出最快速的反应,如此一来,诸如需求分析、文档管理等过程就会出现混乱,甚至是“避而不谈”,等客户需要相关文档的时候再补。其实这样做也无可厚非,因为实际问题摆在眼前,客户希望在最短的时间内看到结果,至于如何实现,他们并不关心。

 

    然而有的项目,在功能性需求的基础上,客户会对其它的非功能性需求作出要求,例如安全性和高效性,有时候甚至会对项目的实现技术进行干预。比如我有碰到有些客户希望采用.NET来实现整个系统,他们并不会在意,各种软件技术诸如C++和C#的差异有多少,更不会去了解各种技术的优缺点在哪里,当时他们给我的理由是:“现在.NET不是很流行么?就用.NET吧。”。好了,现在让我们从架构设计的角度来看整个系统的设计:我们的客户已经给我们划出了“边界线”:请在.NET的范围内考虑问题!当然你完全可以根据实际情况来决定.NET是否真的合适,如果你发现的确不合适,那么你就应该跟你的客户交涉,提出你的观点,然后双方达成一致。

 

    对于效率要求很高的系统,在系统的其它特性上就会有所保留,比如扩展性。在做设计的时候,你或许会把数据访问机制单独划分到一个层中,通过使用工厂模式或者ORM框架来完成数据库操作,这样做的好处是显而易见的:领域逻辑完全不需要关心对象的持久化细节(怎么保存、保存到哪里等一类技术问题),但它所带来的负面影响就是效率不会很高,起码与ADO.NET的方式相比,效率会有所损耗。因此,此时你不得不在扩展性和高效性上做出抉择,抉择的依据是什么?是客户需求,是系统运行环境的资源分配,是系统在未来一段时间的可变性,同时还有你的设计经验。

 

    回过头来再看功能性需求。其实并非所有的功能性需求都是刚性需求,现在我们把功能性需求简单的分为两种:“刚性需求(Must Have)”和“柔性需求(Nice To Have)”。对于刚性需求,没有办法,你不得不将其实现于你的应用系统中,因为如果不做,那软件系统就不具有解决基本领域问题的能力,也自然无法让客户或者用户接受;而对于柔性需求,我认为,应该在可行、稳定的范围内做选择,这时候也应该跟客户共同商讨。比如,假如客户需求的某个功能并非关键功能,但要实现这个功能却需要投入相当大的人力物力资源,或者在技术上没把握,不能确保它能稳定运行,那么我们可以商讨,要么将这个功能的时间延后(比如放入下个阶段或者下下个阶段再完成),要么直接取消这个功能。

 

    事实上,需求中还有很多隐含的东西,就好比人说话会有隐喻一样。例如做数据传输,那数据的加密、校验也就成了一项不可缺少的功能,本文不深入谈论需求分析,所以在此也就不多说了。由此可见,并非所有需求都能决定软件系统的架构,也不是所有的功能性需求。架构选择是功能性需求与非功能性需求的综合考虑因素。还是引用温昱顾问的那句话:关键需求决定架构。

 

    与现行的软件设计方法一样,领域驱动设计(简称DDD,下同)也应该从需求分析开始。需求分析往往需要客户、领域专家以及软件人员共同参与,领域专家和软件人员共同通过客户了解系统需求,在需求的探讨和分析过程中,两者又与客户进行沟通,尽量在领域模型与软件模型之间做到平衡,而软件人员还要从领域专家那得到相关的领域知识。在实际中,客户和领域专家往往不懂软件,而软件人员又往往不了解领域知识,为了解决这个矛盾,DDD引入了通用语言。所谓通用语言,是建立在领域基础上的一套表达工具,可以是图表,可以是文档,甚至可以是简单的几个文字,关键就是要能够缩小客户、领域专家和软件人员之间的交流沟壑。根据DDD经验,UML不适合用作通用语言,因为它太通用了,不足以表示特定领域。通用语言也不应该是需求分析阶段的专利,只要有交流需要,就可以考虑使用通用语言。

 

    与本节开头所述一样,软件项目完全可以没有需求分析,毕竟很多项目需求实在是简单明了,或者这些项目时间太紧,资源上安排不过来,或者客户图价格便宜,对软件质量要求不高。本节介绍需求分析的目的在于阐明在做软件设计的时候,需要权衡各种因素,再选择合适的架构设计,而这些因素又主要来源于需求。本系列文章介绍DDD在.NET下的实践,但并非是DDD的完全照搬,现实系统中,我们将会遇到很多Anti-DDD的东西。

转载至:http://www.cnblogs.com/daxnet/archive/2009/03/06/1686988.html

发表在 C# | 留下评论

【Ajax】CalendarExtender与TextBox Readonly的问题

    最近在看一些.NET Ajax的东西,发现一个小问题,就是当我在将TextBox的ReadOnly属性设置为true的时候,使用CalendarExtender后无法获得TextBox.Text的值。

    在选择日期的时候,不允许用户修改TextBox里的值,这是比较常见的一种做法,目的是不希望再对用户的输入做进一步的格式验证。请看下面的代码:

view plaincopy to clipboardprint?
  1. <asp:TextBox ID=“txtDayOfBirth” ReadOnly=“True” runat=“server” Width=“100px”></asp:TextBox>  
  2. <asp:ImageButton ID=“datePickerImg” runat=“server” ImageUrl=“~/images/calendar.png” AlternateText=“请点击本按钮以选择日期” CausesValidation=“false” />  
  3. <Ajax:CalendarExtender   ID=“dayOfBirthCalendarExtender”    
  4.           runat=“server”    
  5.           Enabled=“True”    
  6.           TargetControlID=“txtDayOfBirth”    
  7.           CssClass=“MyCalendar”  
  8.           PopupButtonID=“datePickerImg”    
  9.           Format=“yyyy年MM月dd日”    
  10.           Animated=“false” />  

    在运行以后,可以通过TextBox边上的日历按钮来选择日期,但是这样做会导致读不到TextBox里文本的问题。解决办法其实很简单,就是,先不在设计中加入ReadOnly属性,而是在Page_Load中动态加入该属性。代码如下:

ASPX页面

view plaincopy to clipboardprint?
  1. <asp:TextBox ID=“txtDayOfBirth” runat=“server” Width=“100px”></asp:TextBox>     
  2. <asp:ImageButton ID=“datePickerImg” runat=“server” ImageUrl=“~/images/calendar.png” AlternateText=“请点击本按钮以选择日期” CausesValidation=“false” />     
  3. <Ajax:CalendarExtender   ID=“dayOfBirthCalendarExtender”       
  4.           runat=“server”       
  5.           Enabled=“True”       
  6.           TargetControlID=“txtDayOfBirth”       
  7.           CssClass=“MyCalendar”     
  8.           PopupButtonID=“datePickerImg”       
  9.           Format=“yyyy年MM月dd日”       
  10.           Animated=“false” />    

C#后台代码

view plaincopy to clipboardprint?
  1. protected void Page_Load(object sender, EventArgs e)   
  2. {   
  3.     if (!Page.IsPostBack)   
  4.         txtDayOfBirth.Attributes.Add(“readonly”“true”);   
  5. }   

    希望本文能帮到遇到类似问题的网友。

转载至:http://www.cnblogs.com/daxnet/archive/2009/03/06/1686987.html

发表在 C# | 留下评论

Adaptive Console Framework 最新版发布(二)

用于在.NET下快速开发控制台应用程序的Adaptive Console Framework框架再次有了新的版本(版本号:3.5.3286.17617)。有兴趣的朋友可以点击这里直接下载安装包使用。

与上一个版本(版本号:3.5.3253.15384)相比,最新版具有如下几个更新:

  • 将框架DLL、演示程序和文档分别打包发布,允许用户根据自己的需要下载ACF的相关组件。针对初级用户提供了InstallShield安装包,安装包里囊括了DLL、演示程序和文档的所有文件。请点击这里获得所有发布包的下载地址
  • 添加了DesignModel的支持。有了DesignModel的支持,用户完全可以根据自己的需要编写控制台应用程序开发工具,这将大大简化控制台应用程序的编写工作。下图是一个基于ACF的控制台应用程序开发工具ACFEditor的界面截屏,您会发现,通过使用ACFEditor,您甚至不需要写任何基础代码就能完成控制台程序框架的搭建(目前本软件仍在开发中)
    2009-3-12 13-41-09

请访问Adaptive Console Framework主页以获得有关ACF的最新消息;有问题的用户可以在Issue Tracker里发布遇到的问题,或者直接发邮件到acf@sunnychen.org以提出您的问题与建议。

转载至:http://www.cnblogs.com/daxnet/archive/2009/03/08/1686986.html

发表在 C# | 留下评论

【领域驱动设计】.NET实践:立足领域

    简单的说,软件开发的目的就是通过计算机解决某一领域的实际问题。这样的定义已经将我们的立足点置于领域层面了:我们需要关注的是领域本身,而不是其它的技术细节。很多人拿到需求,很喜欢从建立数据模型开始,画出数据模型图、ER图,考虑数据库表的结构,以便开始软件设计。比如,假设现在要设计一套简单的学生成绩管理系统,就管理学生各科的成绩,我们都会毫不犹豫的设计三个数据表:Students(用于保存学生信息)、Courses(用于保存学生所学的科目)以及Scores(用于保存某个学生在某个科目上的成绩)。客观的讲,这种做法不能算错,至少对于交付期很短的项目来说,这样做能够更快地达到目的;但从软件本身的角度看,这样做已经本末倒置了。

    早在两年前,我有简单的写过一篇短文,题目是《软件系统设计一定要从数据库设计开始吗?》,有网友评论说,感觉就从来没有从数据库设计开始过,这样很好,证明起码出发点没有错。在这篇文章里我写的比较简单,可能问题没有得到阐明,现在让我们从另一个角度去考虑问题的本质。正如本文开头所述,我们使用软件来解决领域问题,那么领域问题如何映射到软件系统(软件领域)就成了整个软件设计的关键。根据面向对象理论,我们可以通过对象及其之间的关系来反映领域,以面向对象语言来描述领域,这就使得领域问题能够以最自然的方式“翻译”成软件语言。因此,我们接触得最多的就是这些领域对象,因为只有它们才能合理、准确地在软件范围内将领域问题表述清楚。根据DDD,领域对象可以划分为实体、值对象和服务。既然是对象,必然有其生命周期,就会有“创建”、“使用”、“保存”、“取出”、“撤销”等生命周期状态;工厂和仓储管理了领域对象的生命周期。

    现在再来考虑数据库在整个系统中的位置。在DDD中,仓储用来存放、查找领域对象,并在需要时通过保存的数据重建领域对象。使用仓储最大的好处是,领域模型完全不需要考虑数据保存的细节问题(比如如何保存、保存在哪里),从而使领域模型独立于基础结构层。因此,如果我们的数据保存机制选用数据库的话,仓储就会与数据库打交道,将领域对象保存到数据库里。至此,你会发现,数据库并不是位于软件系统的中心位置,甚至可以说,数据库显得并不那么重要了,数据库只不过是一种保存领域对象的机制。从仓储的角度看,它可以把领域对象保存到数据库,当然还能够保存到文件,更极端一点,假设我们的应用服务器7×24不当机,我们可以直接抛弃数据库,让仓储将领域对象保存在内存里,这将大幅度提高系统的性能。因此,仓储为领域模型提供对象的保存、读取、查询等数据服务,而数据库不过是一种技术选择,它位于DDD 4层中的基础结构层,它的主要任务是保存数据,它根本没办法去描述领域问题。

    对象的关系很丰富:继承、实现、聚合、组合等;数据库关系就相对简单了:1:n,n:m。如何将对象关系保存到数据库中,我们可以借助ORM来解决这样的问题,比如NHibernate。同样,ORM位于基础结构层,它不懂领域。在整个软件系统中,数据持久化那是ORM的事情,是用一个数据表保存领域对象,还是使用主从表外键关联的方式保存,都与我们无关。

       好了,到这里我想你也应该可以慢慢地走出数据库的阴影,回归到领域模型本身上。在设计领域模型时,我们又容易踏入另一个误区:认为领域模型就是简单的POCO(Plain Old CLR Objects)及其之间的关系集合。DDD提倡“富领域模型”,这就意味着,应该尽量将业务逻辑置于领域对象里,而对于那些理论上不属于任何领域对象的业务逻辑,应将其置于服务中。这样做的好处是很明显的:因为领域对象是现实世界的面向对象表述,它不但具有属性,而且还应该有自己的行为,它的行为甚至还能触发一些其它的事情。那么什么时候使用POCO?POCO用于构建数据传输对象(DTO),以便能够集中表示数据并让数据在软件系统的各个层间自由传递,它还具有隐藏领域业务逻辑的功能。关于DTO,我会在后续的章节中讨论。

    现在来简单的看一下文章开始部分的那个例子,假如成绩管理系统需要计算每个学生的总分,我想我们的“学生”类大致可以有类似如下的定义:

view plaincopy to clipboardprint?
  1. public class Student : IEntity   
  2. {   
  3.     public Student()   
  4.     {   
  5.         this.DayOfBirth = DateTime.Now;   
  6.     }   
  7.   
  8.     /// <summary>   
  9.     /// 读取或设置学生的姓氏。   
  10.     /// </summary>   
  11.     public string LastName { getset; }   
  12.     /// <summary>   
  13.     /// 读取或设置学生的名字。   
  14.     /// </summary>   
  15.     public string FirstName { getset; }   
  16.     /// <summary>   
  17.     /// 读取或设置学生的出生日期。   
  18.     /// </summary>   
  19.     public DateTime DayOfBirth { getset; }   
  20.     /// <summary>   
  21.     /// 读取或设置学生的成绩列表。   
  22.     /// </summary>   
  23.     public IList<Mark> Marks { getset; }   
  24.   
  25.     /// <summary>   
  26.     /// 获得学生的年龄。   
  27.     /// </summary>   
  28.     public int Age   
  29.     {   
  30.         get { return DateTime.Now.Year – this.DayOfBirth.Year; }   
  31.     }   
  32.   
  33.     /// <summary>   
  34.     /// 计算学生的总成绩。   
  35.     /// </summary>   
  36.     /// <returns>总成绩。</returns>   
  37.     public float GetTotalScore()   
  38.     {   
  39.         float total = 0;   
  40.         foreach (Mark mark in this.Marks)   
  41.         {   
  42.             total += mark.CourseMark;   
  43.         }   
  44.         return total;   
  45.     }   
  46.   
  47.     public override string ToString()   
  48.     {   
  49.         return string.Format(“{0}{1}”,   
  50.             this.LastName, this.FirstName);   
  51.     }  
  52.  
  53.     #region IEntity Members   
  54.     /// <summary>   
  55.     /// 读取或设置学生的编号。   
  56.     /// </summary>   
  57.     public Guid Id { getset; }  
  58.     #endregion   
  59. }   

    本文主要阐述了两个观点:1、软件设计必须立足领域,以领域为关注核心,通过使用面向对象等手段尽可能合理地将领域问题映射到软件系统(领域模型)上;2、领域模型应该是“富领域模型”,否则无法完整、自然地表述领域问题。DDD实践有着很长的路要走,在实践的过程中,我们碰到的问题其实多数没有唯一准确的答案,只能说哪种答案更为合理。作为一名软件人员,我也深刻能够体会到大多数软件人员更喜欢将精力放在解决技术问题上,这样会更容易地得到他人对自己的赏识,然而,当我们真正需要设计一个软件系统时,我们不得不更多地去学习领域知识、关心领域问题,而这些内容大多是跟技术不搭界的。Eric Evans有句话说的很有意思:”Developers get interested in the domain when the domain is technical.”(Domain-Driven Design: Discussion by SVPG),意思是,其实开发人员也关注领域,但是是技术领域。立足领域,DDD之道。

 

转载至:http://www.cnblogs.com/daxnet/archive/2009/03/10/1686985.html

发表在 C# | 留下评论

【领域驱动设计】.NET实践:实体、值对象和数据传输对象

    在DDD中,实体(Entity)、值对象(Value Object)和服务(Service)是领域模型的基本元素;而数据传输对象(Data Transferring Object,DTO)只负责保存数据,以便数据在层与层之间进行传递,这是前两者与DTO的主要区别。

理解实体与值对象

    实体是我们在做开发的时候经常遇见的领域对象,比如上文成绩管理系统中的“学生”就是一个实体,因为在业务处理的过程中,我们必须对“学生”进行区分,“课程成绩”是针对某个学生的概念。由于学生有可能同名同姓,因此不能将姓名作为区分学生的标准,在现实生活中,我们通常是使用“学号”来区分学生。实体是一种领域对象,在特定的上下文(Context)中,我们需要关心的不仅仅是“它是什么”,要更深入地知道“它是哪个”,因此,实体需要有自己的唯一标识符。而值对象又是什么呢?再举一个例子:去超市买东西,假设身上只有几张10元的现金,在结账的时候,掏出了一张10元现金给了收银台,你当然不会去理会刚刚拿来买东西的10元究竟是钱包里的哪张10元,此时,现金就是值对象。从这个例子可以看出,在特定的上下文中,某些领域对象我们只需要知道“它是什么”即可,此时,区分对象的个体变得毫无意义。

    我们往往会不经意地将系统中本应该被定义为值对象的领域对象定义为实体,这种思维定势来自“数据库驱动设计”,因为在做数据表设计时,我们都会在数据表上添加一个PrimaryKey(主键)以区分各条不同的记录。并不是说数据库驱动设计不好,只是这样会使得系统也必须为本不是实体的对象提供标识的维护与管理,这样会影响系统性能。其实并没有一个绝对的标准去判断哪些领域对象应该被视为实体,哪些应该被定义为值对象,实体与值对象的区分需要具体情况具体分析,这就要求软件人员具有一定的设计经验,根据自己的经验作出正确的判断。仍然以上文的“现金”为例,在上文所述的应用场景里,它是值对象,但如果是在一个验钞系统中,它是实体,因为系统需要对个体进行追踪记录,以区分“究竟哪张现金”是伪钞。

.NET里的实体与值对象设计

    事实上,.NET Framework已经对实体与值对象作出了设计。熟悉.NET的朋友都知道,CLR同时支持引用类型和值类型,引用类型内存分配在托管堆里,值类型内存分配在栈里。对于引用类型,系统除了保存其值外,还保存了诸如reference(引用,或者理解为“指针”)、synchronization root等信息。由于reference的存在,CLR就可以通过这个reference来确定一个对象,这明显符合DDD中对实体的定义,reference可以看成是在CLR系统中,对象的唯一标识符。而值类型对象呢?它自然没有这样的标识,因为系统仅仅关心它所保存的值。

    有关“值类型对象”可以被看成“值对象”的证据,请参见这里。在这篇文章中,Jimmy Nilsson同时也指出了LINQ to SQL在支持值对象方面的缺陷,看来.NET在DDD方面的支持还需要进一步加强。

数据传输对象及其应用

    数据传输对象(DTO)的主要任务是传输数据,因此它不会去牵涉任何领域逻辑处理。由于DTO的主要任务是数据传输,因此它具有如下的特性:1、DTO是可以被序列化(Serializable)的,它不能保存一些上下文相关的数据,比如句柄或者引用;2、DTO的定义是简单的,它所能保存的都是一些基本数据类型的数据,或者是一些简单的类(比如String或者DateTime等)的实例,或者是其它的DTO;3、DTO不包含任何业务逻辑。由于DTO需要在各个层中传输,因此需要隐藏业务逻辑的细节,DTO也自然不会包含对业务逻辑的处理过程;4、可以通过装载器(assembler)/拆卸器(disassembler)来实现领域对象与DTO之间的数据转换。

    DTO不是领域对象的映射。出于业务处理封装的需要,领域对象不能“穿梭”于应用系统的各个层面,根据DDD的“富领域模型”观点,领域对象已经不仅仅是POCO,它还具有业务处理能力。如果应用系统各层都能够访问领域对象,那么领域对象的业务处理逻辑也将被暴露在外,这样做的缺点有以下两方面:1、违反封装性需求。假设我们在用户界面层使用领域对象,那么用户界面层也将能够操作领域对象上的业务处理逻辑,而这些操作本不应该由用户界面来完成;2、某些领域对象中的处理逻辑所完成的任务可能比较单一,一个完整的逻辑需要依靠事务来完成,由于领域模型外部对领域的了解几乎为零,所以根本无法得知业务逻辑的正确处理方式。在一无所知的情况下调用领域对象中的处理逻辑是件危险的事情。

    仍然以上文的学生成绩系统为例,假设我们查询到了某个学生在某个学科上的期考成绩,我们需要将这个成绩显示在用户界面上,那么我们就会通过领域对象来查到这个成绩数据,根据该数据组建一个DTO并传给应用层,下面的代码片段简单的展示了这一过程(代码中用了仓储、规约,这些内容将在后续章节中介绍)。

view plaincopy to clipboardprint?
  1. // 界面层   
  2. public class From1 : Windows.Forms.Form   
  3. {   
  4.     private void Form_Load(object sender, System.EventArgs e)   
  5.     {   
  6.         StudentApplication app = new StudentApplication();   
  7.         StudentMarkData data = app.GetMark(studentId, courseName);   
  8.         this.showMarkControl.DataSource = data;   
  9.         this.showMarkControl.Bind();   
  10.     }   
  11. }   
  12.   
  13. // 应用层   
  14. public class StudentApplication   
  15. {   
  16.     public StudentMarkData GetMark(Guid studentId, string courseName)   
  17.     {   
  18.         CourseNameEqualsSpecification specification =    
  19.             new CourseNameEqualsSpecification(courseName); 
  20.   
  21.         CourseRepository courseRepository = new CourseRepository();   
  22.         StudentRepository studentRepository = new StudentRepository(); 
  23.   
  24.         Student student = studentRepository.FindByKey(studentId);   
  25.         Course course = courseRepository.FindBySpec(specification);   
  26.         Mark mark = student.GetMark(course);   
  27.         // 如下的代码从领域对象Student、Course和Mark组装StudentMarkData   
  28.         // 数据,并将数据返回。   
  29.         StudentMarkData data = new StudentMarkData();   
  30.         data.StudentId = studentId;   
  31.         data.StudentName = student.ToString();   
  32.         data.CourseName = course.Name;   
  33.         data.Mark = mark.CourseMark;   
  34.         return data;   
  35.     }   
  36. }   
  37.   
  38. // 领域层   
  39. public class Course : IEntity   
  40. {   
  41.     public string Name { getset; }  
  42.  
  43.     #region IEntity Members   
  44.     /// <summary>       
  45.     /// 读取或设置科目的编号。     
  46.     /// </summary>       
  47.     public Guid Id { getset; }  
  48.     #endregion   
  49.   
  50. }   
  51.   
  52. public class Student : IEntity   
  53. {   
  54.     public Student()   
  55.     {   
  56.         this.DayOfBirth = DateTime.Now;   
  57.     }   
  58.   
  59.     /// <summary>       
  60.     /// 读取或设置学生的姓氏。       
  61.     /// </summary>       
  62.     public string LastName { getset; }   
  63.     /// <summary>       
  64.     /// 读取或设置学生的名字。       
  65.     /// </summary>       
  66.     public string FirstName { getset; }   
  67.   
  68.     /// <summary>       
  69.     /// 读取或设置学生的成绩列表。       
  70.     /// </summary>       
  71.     public IList<Mark> Marks { getset; }   
  72.   
  73.     /// <summary>       
  74.     /// 计算学生某科的成绩。       
  75.     /// </summary>       
  76.     /// <param name=”course”>科目</param>   
  77.     /// <returns>成绩</returns>       
  78.     public Mark GetMark(Course course)   
  79.     {   
  80.         var query = from mark in this.Marks   
  81.                     where mark.Course.Equals(course)   
  82.                     select mark;   
  83.         return mark.First();   
  84.     }  
  85.  
  86.     #region IEntity Members   
  87.     /// <summary>       
  88.     /// 读取或设置学生的编号。       
  89.     /// </summary>       
  90.     public Guid Id { getset; }  
  91.     #endregion   
  92. }   
  93.   
  94. // 公共层(基础结构)   
  95. public interface IDataObject : ISerializable   
  96. {   
  97.     DataObjectStatus DataObjectStatus { getset; }   
  98.     Guid DataObjectId { getset; }   
  99. }   
  100.   
  101. [Serializable]   
  102. [XmlRoot]   
  103. public class StudentMarkData : IDataObject   
  104. {   
  105.     [XmlAttribute]   
  106.     public Guid StudentId { getset; }   
  107.     [XmlElement]   
  108.     public string StudentName { getset; }   
  109.     [XmlElement]   
  110.     public string CourseName { getset; }   
  111.     [XmlElement]   
  112.     public float Mark { getset; }   
  113.   
  114.     public DataObjectStatus DataObjectStatus { getset; }   
  115.     public Guid DataObjectId { getset; }   
  116.   
  117.     public StudentMarkData()   
  118.     {   
  119.         this.DataObjectId = Guid.NewGuid();   
  120.     }   
  121.   
  122.     public StudentMarkData(SerializationInfo info, StreamingContext context)   
  123.         : this()   
  124.     {   
  125.         StudentId = info.GetValue(“StudentId”typeof(Guid));   
  126.         StudentName = info.GetValue(“StudentName”typeof(string));   
  127.         CourseName = info.GetValue(“CourseName”typeof(string));   
  128.         Mark = info.GetValue(“Mark”typeof(float));   
  129.     }  
  130.  
  131.     #region ISerializable Members   
  132.     public void GetObjectData(SerializationInfo info, StreamingContext context)   
  133.     {   
  134.         info.AddValue(“StudentId”, StudentId);   
  135.         info.AddValue(“StudentName”, StudentName);   
  136.         info.AddValue(“CourseName”, CourseName);   
  137.         info.AddValue(“Mark”, Mark);   
  138.     }  
  139.     #endregion   
  140. }   
  141.   
  142.   

    在上面的代码示例中,用户界面层的数据源是一个DTO,而不是领域对象。从分层架构的角度考虑,领域对象位于应用服务器上,为应用系统提供了业务处理的服务。让领域对象跨越服务器边界通过网络传输到客户端,这样做明显不合理。有关用户界面层如何处理DTO以及自动化UI的话题,将在后续文章中简述。

转载至:http://www.cnblogs.com/daxnet/archive/2009/03/31/1686984.html

发表在 C# | 留下评论

Windows Live Writer插件:在WLW中插入语法高亮代码(二)

    在 上文 中,我发布了一个在WLW中插入语法高亮代码的插件,该插件使用tablimagee的HTML 标记来分隔代码与行号。这样做的一个问题是,针对不同的博客主题,行 号列的宽度可能需要手动调整,而且大量的<td></td>标记使得博客文章变得很大。为此,我修改了插件源代码,使用空格来分隔代码与行号。这样做其实也有弊端,比如代码折行的时候,折行后的第一个字符会与行号的第一个字符同列,这会使行号与代码变得混淆难以分辨。此外,在读者Ctrl+C复制代码的时候,会连同行号一起复制过去。

    您可以点击 这里 下载这个更新后的语法高亮插件,在下载后,直接解压到WLW的Plugins目录下,重启WLW即可使用。注意,在复制的时候要先退出WLW,否则会出现文件替代的共享冲突。

    下面的代码就是通过该插件插入的代码片段,展示于此给读者参考。

  • C#

    using System;
    using System.Reflection;
    using System.IO;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    namespace ConsoleApplication3
    {
    10     class TGeneric<T>
    11     {
    12         public T Get(T _t)
    13         {
    14             return _t;
    15         }
    16     }
    17
    18     class TGeneric<T, U>
    19     { }
    20
    21     class Program
    22     {
    23         static string GetGenericTypeSignature(Type genType)
    24         {
    25             if (!genType.IsGenericType)
    26                 return genType.Name;
    27             StringBuilder result = new StringBuilder();
    28             result.Append(genType.Name.Substring(0, genType.Name.IndexOf(`)));
    29             result.Append(<);
    30             Type[] genericArguments = genType.GetGenericArguments();
    31             for (int i = 0; i < genericArguments.Length; i++)
    32             {
    33                 result.Append(genericArguments[i].Name);
    34                 if (i != genericArguments.Length  1)
    35                     result.Append(,);
    36             }
    37             result.Append(>);
    38             return result.ToString();
    39         }
    40         static void Main(string[] args)
    41         {
    42             foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
    43             {
    44                 if (type.IsGenericType)
    45                 {
    46                     Console.WriteLine(GetGenericTypeSignature(type));
    47                 }
    48             }
    49         }
    50     }
    51 }
    52

     

  • Borland Delphi
    unit uMain;

    interface

    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, ComCtrls, Menus, ToolWin, ExtCtrls, ActnList;

    type
    10   TfrmMain = class(TForm)
    11     StatusBar: TStatusBar;
    12     MainMenu: TMainMenu;
    13     F1: TMenuItem;
    14     CoolBar: TCoolBar;
    15     MainToolBar: TToolBar;
    16     ToolButton1: TToolButton;
    17     ObjectTree: TTreeView;
    18     Splitter1: TSplitter;
    19     ActionList: TActionList;
    20     ac_New: TAction;
    21     New1: TMenuItem;
    22     procedure ac_NewExecute(Sender: TObject);
    23   private
    24     { Private declarations }
    25   public
    26     { Public declarations }
    27   end;
    28
    29 var
    30   frmMain: TfrmMain;
    31
    32 implementation
    33
    34 uses uNewList, uGlobal, uDataDictionary;
    35
    36 {$R *.dfm}
    37 procedure TfrmMain.ac_NewExecute(Sender: TObject);
    38 var iRet: Integer;
    39     dd: TDataDictionary;
    40 begin
    41     frmNewList.ShowModal;
    42     iRet := frmNewList.GetModuleResult;
    43     if iRet = MODRES_CANCEL then Exit;
    44     dd := TDataDictionary.Create(frmNewList.GetConnectionString);
    45     try
    46         dd.BuildDataDictionary;
    47     except
    48         on E: Exception do MessageDlg (E.Message, mtError, [mbOK], 0);
    49     end;
    50     dd.Destroy;
    51 end;
    52
    53 end.
    54

 

    在使用的过程中有什么问题,请直接回复本帖联系我,谢谢您的支持!

转载至:http://www.cnblogs.com/daxnet/archive/2009/05/06/1686982.html

发表在 C# | 留下评论