理解并实现生成器模式

介绍 本文讨论生成器设计模式,讨论该模式什么情况下使用,怎么实现。并且。最后会有一个简单的生成器模式的实现。 背景 当我们的程序需要创建一个对象。而这个对象必须由很多不同的对象来构造的时候。为了构造最后的对象。我们不得不组合那些部分对象。最后我们会发现我们的代码被各种各样的部分对象的细节所弄的难以理解 为了说明上面的情况。我们做一个手机生产制造系统的例子。假定我们我们有一个已经安装在手机供应商那块的一个系统。现在供应商系那个要根据一些参数来创造一个新手机。比如触屏,操作系统,电池等。如果我们已经有了这些部分的对象,那么上述部分的任意组合将会导致客户端代码复杂难以管理。比如决定生产哪种手机的模块。 生成器模式目的就是解决上述问题的。GoF定义生成器模式如下: Separate the construction of a complex object from its representation so that the same construction process can create different representations. 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 这个定义意味着我们不得不设计这个系统。通过一种客户端仅仅定义参数,而生成器则接管创建复杂对象 的方式。我们看一下生成器模式的类图。 然后看看上图中的每一个类都表示什么 ConcreteBuilder: 创建复杂产品的具体类.将会知道他已经创建的Product(产品),也就是他已经装配了的Product, 客户端通过该类得到Product对象. Builder: 创建Product的接口 Director: 客户端代码,定义了哪些部分应该被组合在一起来创建具体的Product Product: 这是通过组合很多部分创建的对象 使用代码 我们现在跟随上述的定义,然后试着去实现一个基本的生成器模式 我们先在合适的地方定义Product的不同部分,我们简单的定义一些枚举类型,那么我们就可以通过组合不同的部分创建Product了。 // 一些helper枚举定义各种零件 public enum ScreenType { ScreenType_TOUCH_CAPACITIVE, ScreenType_TOUCH_RESISTIVE, ScreenType_NON_TOUCH }; public enum Battery { MAH_1000, MAH_1500, MAH_2000 }; public enum OperatingSystem { ANDROID, WINDOWS_MOBILE, WINDOWS_PHONE, SYMBIAN }; public enum Stylus { YES, NO }; 然后,我们看一下Product类,我们需要有一个可以通过装配创建的Product类,这里我们定义一个MobilePhone类,也就是概念里的Product类了。 // 这是 "Product" 类 class MobilePhone { // 不同部分的字段 string phoneName; ScreenType phoneScreen; Battery phoneBattery; OperatingSystem phoneOS; Stylus phoneStylus; public MobilePhone(string name) { phoneName = name; } //公有属性访问这些部分 public string PhoneName { get { return phoneName; } } public ScreenType PhoneScreen { get { return phoneScreen; } set { phoneScreen = value; } } public Battery PhoneBattery { get { return phoneBattery; } set { phoneBattery = value; } } public OperatingSystem PhoneOS { get { return phoneOS; } set { phoneOS = value; } } public Stylus PhoneStylus { get { return phoneStylus; } set { phoneStylus = value; } } // 显示手机相关信息的方法 public override string ToString() { return string.Format("Name: {0}\nScreen: {1}\nBattery {2}\nOS: {3}\nStylus: {4}", PhoneName, PhoneScreen, PhoneBattery, PhoneOS, PhoneStylus); } } ...

2012-10-08 · 3 min · bystander

接口VS 委托

背景 对于指定的任务有不同的方案可供选择,通常是很好的。因为可能某一种方案会更加适合该任务,但是有时候做决定会很难。因为这些不同的方案有其各自的优缺点。 我经常会停下来好好想想,是不是接口比委托更适合或者是更不适合某个任务。有时候我甚至会回去看我写的代码,这些代码刚开始使用委托来实现,我后来用接口替换掉。因此,是时候写篇文章来阐述一下这两种技术的优缺点了。 性能 我经常看到有人问接口是不是比委托更快啊。或者是不是相反。通常。别人给的答案会是: 接口更快。委托相当慢 委托更快,因为他们是指向方法的指针,接口则需要一个v-table(虚函数解析表),然后找到委托 他们一样快,但委托更容易使用 好吧。那些都是错的。也许在.Net 1中。委托真的很慢。但是事实是: 委托执行(execute)的时候更快 接口获得(get)的时候更快 在下面这段代码中: Action action = SomeMethod; 我们将得到一个Action(委托类型)来调用SomeMethod。问题是:委托是包含被调用方法的实例和指针的引用类型。而不仅仅只是一个指向方法的指针,通过引用类型,委托需要分配内存,因此,每一次你把一个方法变换成一个委托的时候。都会分配一个新的对象。 如果委托是一个值类型。会有些不一样。但是他们不是。。 另一方面,如果我们这样写代码: IRunnable runnable = this; 如果实现了IRunnable接口的对象。我们简单通过一个转换得到同样的引用。没有涉及内存分配。我们将可以通过下面的代码来进行速度比较: 对于委托: Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); for(int i=0; i<COUNT; i++) { Action action = SomeMethod; action(); } stopwatch.Stop(); Console.WriteLine(stopwatch.Elapsed); 对于接口 Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); for(int i=0; i<COUNT; i++) { IRunnable runnable = this; runnable.Run(); } stopwatch.Stop(); Console.WriteLine(stopwatch.Elapsed); 我知道接口会更快。不是因为他们执行更快。而是因为每一次迭代,一个新的Action委托都会被分配。 但是。如果把委托和接口的获得语句放在循环之外。委托会更快一些。 当创建事件的时候。举个例子。我们在给事件添加委托的时候,就只添加一次。这样。即使事件被触发再多次。也只进行了一次内存分配。 那么?谁赢了? 好。对于事件,委托将会更快些。 但是。在说委托更好或是更快之前,我们再看另一种情况。 匿名方法 在我看来,匿名方法是委托最糟糕的使用。但是同时。也正在变成最普遍的用法。 当你像这段代码这样调用的时候 for(int i=0; i<count; i++) MethodThatReceivesADelegate(() => SomeCall(i)); 事实上,编译器将会创建一个接受参数i的方法实例,然后创建另一个实例(即委托)来引用这个实例。 如果用接口来替换的话。编译器将指挥分配单一的对象,该对象实现了接口 可能的抱怨 一些人也许对那个接受参数i的方法实例的问题有所疑惑。他们可能认为,在每一次迭代中。实例里面的之被改变了。也许编译器可以优化这个委托的内存分配。实际只分配了一次。 好把。对于委托的内存分配我不知道。但是。对于要分配一个接受参数i的单一实例,确实真的。也是一个bug。如果MethodThatReceivesADelegate 把委托传递给另一个线程。其他的线程也许会接收到错误的i值,在.net 4.5中。这块不会出错。因为。每一次迭代。一个新的对象被创建。这就能保证当委托被传递到另一个线程里的时候。结果总是正确的。但这也就意味着每次都会有一个新的委托会创建。 如果MethodThatReceivesADelegate 仅仅使用委托一次。使用接口也许更好。不幸的是。我们没有办法实现匿名接口。 好。如果是为了方便。委托更好。但是如果要求性能。在这种情况下。接口会更好。因为避免了一次不必要的内存分配。 事实上,我创建了IRunnable接口是为了强制使用者实现了一个新的类型,而不是使用匿名委托。这样就可以解决在for循环(或是任何在foreach里使用的任何值)i值可变的问题,同时也有一定的性能提升。。 调用和动态调用 现在我们知道有匿名委托,但是没有匿名接口,只使用一次的情况下,接口将会比委托有更好的性能。因为只请求一个对象而不是两个。 这让我开始考虑,当一个方法接受一个只会执行一次的方法作为参数的时候,我应该使用委托还是是用接口。 但是。更多的性能相关的情况下我们可以这样用。。 你是否曾经有过要用动态调用代替直接委托调用的情况?一般是在编译时并不知道委托的参数类型的情况下。 好。当有一个接口。在接口的方法里有一个方法调用的参数类型未定。我不知道为什么。但是反射调用和委托的动态调用都极慢。比直接对参数做转换都慢。而数组长度验证。然后放到一个try/catch块里会抛出TargetInvocationException 异常。 因此。如果你有类似下面的代码: public interface IDynamicInvokable { object DynamicInvoke(params object[] parameters); } 那么你可以创建你的委托接口,是IDynamicInvokable 接口的继承接口,像这样: public interface IAction<T>: IDynamicInvokable { void Invoke(T parameter); } 这样你的用户就可以通过Invoke方法调用你的接口,如果他们在编译时不知道接口的类型。他们可以使用更泛化一些的IDynamicInvoke。 注意:我讨厌泛型这个名字。对于我来说。IDynamicInvoke 是调用方法最泛型的的途径,而IAction<T> 是类型接口,因此,我我说泛型的时候。我其实是在说更加普遍无类型的调用泛型。而不是类型指定的泛型。 那么,如果对委托做上千次调用。但是使用DynamicInvoke 代替Invoke,接口会做的更好 我又一次的问我自己。匿名委托的简单性值得吗?仅仅为了更好的性能我就把让我的用户对我方法的调用变得困难?并且这真的会影响到程序的整体性能吗? 泛型,差异,无类型的使用 我刚刚说我讨厌泛型的名字。因为使用泛型的代码通常是有类型的代码,而我们也许需要的无具体类型的代码,我认为这样更加泛一些。 但是。让我好好讨论一下.net的泛型。假设你知道一个委托的参数数目,但是你不知道具体的类型,这和DynamicInvoke 是不一样的。因为这个方法。简单的把所有的参数当成一个数组。 泛型具化或者是相反可以有一些帮助。但是很小。 比如。我们可以把 Func&lt;string&gt; 当成 Func&lt;object&gt; ``,或是把``Action&lt;object&gt; 看成 Action&lt;string&gt; 理由很简单,当返回一个值的时候(``Func``的情况),``string``是一个``object``。将不会做任何转换。将会简单地返回一个``string``,调用这会看成一个无类型的``object``。但是可以。而在``Action``这个情况下。它需要一个``object``,而``string.是可用的object,所以也可以。 但是。如果你想要把Func&lt;int&gt; 当作Func<object>。更广泛一点。想把所有的参数转换成object,可以吗? ...

2012-10-07 · 2 min · bystander

YAXLib---- XML序列化神器

今天早上翻译了Yet-Another-XML-Serialization-Library-for-the-NET,刚开始以为很短。翻译着发现不对。。然后你不逼你自己。怎么知道自己做不到。于是。将近4个小时把30页的文档翻译完了。因为文章很长。所以本文只列出前两部分。我把翻译好的做成了pdf, 文档下载:XML序列化神器 1 介绍 在本文中,会把要提到的XML序列化库叫做YAXLib,我们知道。.Net 还是提供了一些序列化功能的,尤其是XmlSerializer,该类被程序员广泛使用用来序列化对象成XML,当然,反序列化也是可以的。我认为XmlSerializer类的问题有几下几点 程序员不能自由的选择生成的xml的结构 不支持序列化一些集合类,比如Dictionary<,> 或者IEnumerable<>的属性 当反序列化的时候,如果缺失了一些域,则反序列化失败,这就使得用来存储一ixekeyi被用户编辑的配置文件变得不合适了。 2 为什么使用YAXLib YAXLib解决上述问题的特点 程序员可以决定xml文件的结构,一个属性可以是一个子元素,或者是其他属性的属性,或者是一个在类中没有对应属性的元素。 集合类也可以被序列化成一个逗号分隔(也可以是其他任何分隔符)的数据项列表,而且。为Dictionary<,>对象实现了一些特殊的格式化功能,这样,使得程序员可以完全控制生成的xml文件的结构 他支持System.Collections.Generic 命名空间中的所有泛型集合类(像Dictionary, HashSet, LinkedList, List, Queue,SortedDictionary, SortedList, 和 Stack) 和在System.Collections 命名空间中的非泛型集合类( ArrayList, BitArray, Hashtable, Queue, SortedList, 和 Stack)非泛型集合类可以包含多种不同的对象,而且,库还支持序列化和反序列化一维,多维,不规则的数组。 支持通过对基类/接口的引用,实现对一些对象集合的序列化和反序列化。 支持多级反序列化 程序员可以为生成的xml提供注释 当进行反序列化的时候,程序员可以选择性对于那些与类的属性相关,但没有出现在xml文件中的数据应该如何处理。这种情况下可以看错是一个错误,然后类库抛出一些异常,或者记录错误,或者可以被看成一个警告,然后用程序员预定义的值赋给对应的属性,而且,程序可以可以选择忽略这个问题,相关的异常将既不抛出也不作任何记录。请查看保留空引用标识那一节 看看什么时候可以忽略孤立的数据也许对你有帮助 程序员可以自己选择错误处理规则,对于数据敏感的应用程序,程序员可以选择在任何异常的情况下,库都应该抛出并且记录异常,对于其他的一些情况(比如要求不那么高的配置文件的存储),程序员可以选择把异常仅仅看成一个警告,仅仅记录一下,让程序的其他部分继续运行。 文档下载:XML序列化神器 著作权声明:本文由http://leaver.me 翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!

2012-10-05 · 1 min · bystander

简单扩展方法增强代码可读性

本文技术含量不高,但是思路可以借鉴。。 介绍 当你处理计时器,时间间隔,或是其他关于日期的计算的时候。你必然会使用TimeSpan类。 我觉得写出下面的代码可读性并不好。。 // 1个表示5小时的时间间隔 var theTimespan = new TimeSpan(0, 5, 0, 0, 0); 而下面的代码就要好一些 //一个表示5小时的时间间隔 var theTimespan = 5.Hours(); ** 扩展方法** 使用这些扩展了int类的方法。可以使得创建TimeSpan可读性更好 public static TimeSpan Days(this int value) { return new TimeSpan(value, 0, 0, 0, 0); } public static TimeSpan Years(this int value) { var dt = DateTime.Now.AddYears(value); return (dt - DateTime.Now).Duration(); } public static TimeSpan Hours(this int value) { return new TimeSpan(0, value, 0, 0, 0); } public static TimeSpan Minutes(this int value) { return new TimeSpan(0, 0, value, 0, 0); } public static TimeSpan Seconds(this int value) { return new TimeSpan(0, 0, 0, value, 0); } public static TimeSpan Milliseconds(this int value) { return new TimeSpan(0, 0, 0, 0, value); } 许可 本文所有源代码包括文件在CPOL下授权。。 原文地址:Simple-extension-methods-for-code-readability 著作权声明:本文由http://leaver.me 翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!

2012-10-04 · 1 min · bystander

11个高效的VS调试技巧

介绍 调试是软件开发周期中的一个很重要的部分,有时很有挑战性,有时候则让程序员迷惑,有时候让程序员发疯,但是。可以肯定的是,对于任何不是太那个微不足道的程序来说,调试是不可避免的。近年来,调试工具的发展已经使得很多调试任务简单省时了。 本文总结了十个调试技巧,当你使用VS的时候可以节省你很多时间。 1. 悬停鼠标查看表达式 调试有时候很有挑战性,当你步入一个函数想看看哪块出错的时候,查看调用栈来想想值是从哪来的。另一些情况下,则需要添加一些监视表达式,或者查看局部变量列表,这通常还是花费一些时间的,但是。如果你把你鼠标指向你感兴趣的一个变量。你会发现事情简单多了。而且,类和结构体可以通过单击展开。这样。你就可以方便快捷的找到你想查看的变量了。 2. 实时改变值 调试器不仅仅是一个分析程序崩溃或是异常结果的工具了,许多bug都可以通过步入新写的函数,检查函数是否如期望的那样运行来预防。有时候你可能会好奇“如果条件为真函数会正确运行吗”大多数情况下,根本不需要改变代码重启挑起,仅仅把鼠标悬停到一个变量上,双击值然后输入一个新值就可以了。。 3.设置下一条语句 一个典型的调试情况就是通过单步跟踪分析为什么一个函数调用失败了。当你发现一个函数调用的另一个函数返回错误的时候你会怎么做?重启调试?有更好的方法。拖动这个黄色的语句标识到你想下一步执行的语句前就可以了。比如你刚才失败的那块,然后步入。简单,不是吗? 4.编辑然后继续 调试一个复杂的程序,或是一个插件的时候,在一个被调用很多次的函数处发现一个错误。但是不想浪费时间停下来,重新编译然后重新调试。没问题,仅仅在该处改正代码然后继续单步就可以。VS会修正程序然后继续调试不需要重启 注意,编辑然后继续有大量的已知限制,首先,64位代码是不行的。如果他如果为你的C#程序工作。就去工程设置的生成选项,然后目标平台为x86.不要担心。发布版的目标平台和调试的时候是分开的。可以被设置为任何平台。。 第二.编辑然后继续改变在一个方法里应该是局部的。。如果你改变了方法签名,添加一些新方法或是类。你就不得不重启程序了。或者撤销改变来继续。改变方法也包含lambda表达式隐式修改的自动生成的代理类,因此也不能继续。 5.方便的监视窗口 大概现代的调试器都有一个监视窗口,无论如何。VS允许你简单的添加或移除变量。单击空行,输入你的表达式按下回车,或者是在不需要的表达式上按下Delete键就可以删除了。 而且。从监视窗口你不仅仅可以看到“正常”的变量。你可以输入$handles 来追踪你的程序打开了多少句柄(可以方便的修复内存泄漏) ,输入$err 可以看到上一个函数的错误码,然后使用工具-错误信息可以看到更详细的描述,或者输入@eax(64位是@rax)来查看包含函数返回值的寄存器。 6.带注释的反汇编 使用交互式的反汇编模式可以使得优化程序的关键部分变得很容易,VS给出对应你代码每一行的汇编指令,并且运行单步运行。同时,可以在任何位置设置断点。而且,表达式的查看和修改也像在C++代码里一样 7.带有栈的线程窗口 调试多线程的程序是痛苦的。。或者也可以是很有趣的。取决于你的调试器。VS2010真正优美的特性是线程窗口的栈视图,通过窗口的调用栈你可以方便的总览线程。 8.条件断点 如果你尝试通过断点再现一个罕见的事件,该情况引发了一些严重的错误。你可以添加条件断点。定义一个断点的条件,然后如果条件不成立,VS会忽略该断点 9.内存窗口 有些bug由不正确的结构体定义引起,忽略的对齐属性等等。查看内存中的内容可以定位然后修复bug。VS提供了一个放百年的内存窗口,可以把值以8/16/32/64位的形式展示。还有浮点值。也允许实时改变他们。就像在文本编辑器里一样。 10.转到定义 这个特性不是直接关于调试的,而是关于浏览大项目的。如果你尝试找到一些不是你自己写的代码中的错误,快速知道“这个类型是什么”或者“这个函数是干嘛的”,可以节省很多时间,VS通过一个转到定义命令方便了你。 11.命令窗口 第十一的技巧chaau已经建议过了。确实可以节省很多时间,VS支持命令窗口,可以通过,视图-其他窗口-命令窗口来启动。一旦激活,你可以输入不同的命令来自动化调试。举个例子。你可以通过如下命令 简单的模拟MFC COleDateTime 变量。 ? dt.Format("%Y-%m-%d %H:%M:%S") 许可 本文包括源代码和文件在CPOL下授权。 原文地址:10plus-powerful-debugging-tricks-with-Visual-Studi 著作权声明:本文由http://leaver.me 翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!

2012-10-03 · 1 min · bystander

C#编写文件搜索器

介绍 在装有Vista的机器上。我想通过一个给定的字符串来搜索我硬盘上的一个文件,该文件内容包含这个字符串序列,资源管理器是做不到的。因此,我就决定自己写吧。然后就写成这样了。。 我做了什么 你必须输入一个选择一个搜索目录,这样程序才知道在哪搜索文件/目录,如果你选上了“包含子目录”复选框,程序就会递归地搜索指定目录的子目录,指定的文件名可以是像 “.wav;.mp3;Christma??ree.*” 这样的字符串,程序将会列出所有的文件/目录匹配这些文件名 你也可以使用一些限制条件来限制找到的项目,每一个限制条件可以通过选上复选框来激活,限制条件的参数可以在右边选中就行了。 1. “Files newer than”将会列出LastWriteTime(上次修改时间)晚于指定时间的文件 2. “Files newer than”将会列出LastWriteTime(上次修改时间)早于指定时间的文件 3. “Files containing the string"仅仅列出包含字符串参数的文件。 程序将会把字符串转换成字节序列,可以使用ASCII或者Unicode编码,取决于你的选择,然后搜索每一个出现这个字节序列的文件。 点击Start(开始)按钮就开始搜索了。找到的项目会列在下面,如果搜索时间太长了。你可以点击Stop(停止)来停止搜索。 如果你双击下面的一个文件。不是文件夹哦,程序将会根据关联程序打开该文件 如果你邮件一个项目,然后选择“Open Containing Folder”(打开包含文件夹)将会在资源管理器里打开包含该项目的文件夹 如果你想要把搜索结果保存到一个文本文件。输入个分隔符分隔项目,然后点击“Write results to text file…”(保存结果到文本…) 使用代码 1. MainWindow处理所有的界面事务 2. Searcher类提供了业务逻辑,用来搜索FileSystemInfo对象 当用户点击Start(开始)按钮,Searcher.Start 方法就会执行,该方法开启了一个名为SearchThread 的新线程,这个线程搜索文件/目录,匹配用户输入的参数,如果找到了一个匹配的FileSystemInfo对象,它就出发一个异步的FoundInfo 事件,然后MainWindow就可以从FoundInfoEventArgs中解出FileSystemInfo对象,然后更新结果列表,当线程结束的时候,将m_thread成员对象设置为null,每一次Searcher.Start 执行的时候都会检测m_thread是否为null,因此同时不会有两个线程在运行。 当用户点击Stop(停止)按钮的时候Searcher.Stop 方法被执行,然后设置m_stop 成员为true, Searchthread会注意到这个改变。注意本操作是线程安全的。因为布尔变量只需要一步就操作完成了 重要:在Searcher_FoundInfo 事件处理中,MainWindow使用Invoke方法通过代理来调用this_FoundInfo 方法。通过这个方法,MainWindow是的更新结果列表的代码在MainWindow的线程里执行,而不是在Searcher的线程里,直接调用this_FoundInfo 方法会引发程序崩溃,因为Searcher_FoundInfo 事件处理和图形界面控件不同步。 private delegate void FoundInfoSyncHandler(FoundInfoEventArgs e); private FoundInfoSyncHandler FoundInfo; ... private void MainWindow_Load(object sender, EventArgs e) { ... this.FoundInfo += new FoundInfoSyncHandler(this_FoundInfo); ... } ... private void Searcher_FoundInfo(FoundInfoEventArgs e) { if (!m_closing) { this.Invoke(FoundInfo, new object[] { e }); } } private void this_FoundInfo(FoundInfoEventArgs e) { CreateResultsListItem(e.Info); } CreateResultsListItem 方法创建并添加一个ListViewItem 到结果列表中,然后展示FilesystemInfo 对象包含的数据,FileSystemInfo 可以是FileInfo 或是DirectoryInfo ,取决于Searcher 找到的结果, is操作符可以用来判断对象的类型,如果是FileInfo独享,列表还会以KB为单位显示文件大小 ListViewItem.ListViewSubItem lvsi = new ListViewItem.ListViewSubItem(); if (info is FileInfo) { lvsi.Text = GetBytesStringKB(((FileInfo)info).Length); } else { lvsi.Text = ""; } 当Searcher 线程结束的时候,触发ThreadEnded 事件,因此,MainWindow可以注意到搜索结束,Searcher_ThreadEnded 事件处理句柄使用和Searcher_FoundInfo一样的方式调用Invoke方法。 private delegate void ThreadEndedSyncHandler(ThreadEndedEventArgs e); private ThreadEndedSyncHandler ThreadEnded; ... private void MainWindow_Load(object sender, EventArgs e) { ... this.ThreadEnded += new ThreadEndedSyncHandler(this_ThreadEnded); ... } ... private void Searcher_ThreadEnded(ThreadEndedEventArgs e) { if (!m_closing) { this.Invoke(ThreadEnded, new object[] { e }); } } private void this_ThreadEnded(ThreadEndedEventArgs e) { EnableButtons(); if (!e.Success) { MessageBox.Show(e.ErrorMsg, "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } } Demo下载 FileSearcher 许可 本文包括源代码和文件在CPOL下授权 ...

2012-10-01 · 1 min · bystander

CSV导入导出工具

介绍 本文介绍并且实现了在平面文件CSV和SQL server之间的导入导出功能。使用VS2005写的。使用了.net 2.0 本文基于前一篇文章:从CSV导入数据并存储到数据库,本文包含了新功能,比如,导出功能,在数据库创建表,批量拷贝。接下来的例子中有很多注释。 导入 通过ODBC驱动连接到一个CSV文件,然后把文件读到一张表了(基于前面提到的那篇文章) 使用不同的编码和不同的行分隔符(基于前文) 加载CSV文件到DataSet(基于前文) 如何显示对CSV文件的预览(基于前文) 通过SqlBulkCopy的对象向SQL server转移数据,原始数据是DataSet实例 使用结构表,基于CSV文件,创建一个新表 使用事件来处理批量拷贝的进程 导出 浏览SQL 数据库中的用户表 使用不同的编码和分隔符 使用SqlDataReader读取数据,使用StreamWriter转移数据到平面文件 使用 下载工程 新建一个数据库,或者使用一个存在的数据库 修改软件中的数据库连接字符串,在prop.cs文件中 public static string sqlConnString = "server=(local); database=Test_CSV_impex;Trusted_Connection=True"; 运行工程 一些代码段 加载CSV到DataSet中 /* *加载CSV到DataSet. * * 如果numberOfRows parameter 是 -1, 就加载所有行, 否则就加载指定数目的行 */ public DataSet LoadCSV(int numberOfRows) { DataSet ds = new DataSet(); try { // 创建并打开ODBC连接 string strConnString = "Driver={Microsoft Text Driver (*.txt; *.csv)}; Dbq=" + this.dirCSV.Trim() + "; Extensions=asc,csv,tab,txt;Persist Security Info=False"; string sql_select; OdbcConnection conn; conn = new OdbcConnection(strConnString.Trim()); conn.Open(); //创建SQL语句 if (numberOfRows == -1) { sql_select = "select * from [" + this.FileNevCSV.Trim() + "]"; } else { sql_select = "select top " + numberOfRows + " * from [" + this.FileNevCSV.Trim() + "]"; } //创建数据适配器 OdbcDataAdapter obj_oledb_da = new OdbcDataAdapter(sql_select, conn); //用CSV的数据填充DataSet obj_oledb_da.Fill(ds, "csv"); //关闭连接 conn.Close(); } catch (Exception e) //异常处理 { MessageBox.Show(e.Message, "Error - LoadCSV", MessageBoxButtons.OK,MessageBoxIcon.Error); } return ds; } 通过SqlBulkCopy从ODBC连接中转移数据到数据库 ...

2012-09-30 · 4 min · bystander

ListView布局管理器

介绍 使用ListViewLayoutManager 可以控制ListView/GridView列的布局 固定列宽:有着固定列宽的列 范围列宽:有着最小最大宽度的列 比例列宽:成比例的列宽 范围列宽可以限制列的宽度,也包括填充列的剩余可视区域。 据我们了解的Html中的表格和Grid空间。比例列以一个百分比来定义列宽,以下几个因素共同确定了比例列的宽度。 垂直ListView滚动条的可视与否 ListView控件宽度的改变 非比例列宽度的改变 本程序支持通过XAML或是后台代码来控制ListView。如果通过XAML来控制。则允许ListViewLayoutManager 被附加到一个存在的ListView控件上。 ConverterGridColumn 类通过接口IValueConverter 提供了对象绑定。使用ImageGridViewColumn 类则允许通过DataTemplate(数据模板)将列显示成图片等。 在 User Setting Applied中,我展示了如何固定ListViewlieder顺序和大小 XAML中ListView/GridView布局 固定列 下面的例子展示了通过XAML使用固定列宽控制列 <ListView Name="MyListView" ctrl:ListViewLayoutManager.Enabled="true"> <ListView.View> <GridView> <GridViewColumn DisplayMemberBinding="{Binding Path=Name}" ctrl:FixedColumn.Width="100" Header="Name" /> <GridViewColumn DisplayMemberBinding="{Binding Path=City}" ctrl:FixedColumn.Width="300" Header="City" /> </GridView> </ListView.View> </ListView> 设置附加到ListView控件上的ListViewLayoutManager 的Enabled属性为True。然后FixedColumn.Width 就会阻止鼠标拖动改变列的宽度。 比例列 下面的例子展示了使用XAML通过比例来控制列 对比Grid控件的RowDefinition.Width 属性,ProportionalColumn.Width会计算百分比。简单来说,就是上面的例子中Name列会占到总宽度的25%,而City列占到75%。 与固定列相似。鼠标将不能改变列的宽度。 范围列 下面的例子展示了使用XAML通过最小/最小宽度来控制列 第一个范围列的IsFillColumn 属性被设置为True,因此将会自动改变大小来填满剩余的空间,而如果ListView包含一个比例列的话,范围列将不会填充 通过鼠标可以拖动范围列的宽度。鼠标指针会有一些提示。。 组合使用 在真实的世界里。组合使用很普遍。他们的顺序可以多种多样。 使用后台代码控制ListView/GridView布局 ListView listView = new ListView(); new ListViewLayoutManager( listView ); // attach the layout manager GridView gridView = new GridView(); gridView.Columns.Add( FixedColumn.ApplyWidth( new MyGridViewColumn( "State" ), 25 ) ); gridView.Columns.Add( RangeColumn.ApplyWidth( new MyGridViewColumn( "Name" ), 100, 150, 200 ) ); // 100...200 gridView.Columns.Add( ProportionalColumn.ApplyWidth( new MyGridViewColumn( "City" ), 1 ) ); // 33% gridView.Columns.Add( ProportionalColumn.ApplyWidth( new MyGridViewColumn( "Country" ), 2 ) ); // 66% listView.View = gridView; 定制列的效果 类ConverterGridColumn 作为一个基类,用来绑定列到独立的对象。 ...

2012-09-29 · 3 min · bystander

从数据库读取图片发生“无效的参数”异常

介绍 我发现对于很多人来说,当从数据库里载入一张图片然后重新创建成一张图片显示的话会有这样一个问题—-当他们尝试重新创建新的图片的时候,会抛出一个“无效的参数”异常 因此,本文介绍该异常如何产生。并且我希望未来当我或是你发生这个错误的时候还能有所帮助。。 背景 存储图片到数据库里面是一个很有效的想法。很多人在一些场合都会这样做。的确,这是一个很好的想法。在图片很小的情况下,或者图片不是太多。在这两种情况下,当你需要图片的时候,你会实时去加载它们。而当你不需要的时候如果从数据库里加载图片会浪费很多带宽。并使得你的程序有一些慢。 但问题是这种方法也很容易发生错误。–尤其是你使用字符串连接,然后组合到你的SQL语句里面—并且这个错误只有当你打算使用存储的信息的时候才会发生。然后,看起来似乎是你的读取代码写错了—不可能—它是正常的。我在其他地方都可以的。。 从数据库里加载图片 重数据库里读取一张图片然后转换成图片显示是很简单的。 using (SqlConnection con = DBAccess.DBCon) { using (SqlCommand cmd = new SqlCommand("SELECT picture FROM Pictures WHERE Id=@ID", con)) { cmd.Parameters.AddWithValue("@ID", Id); SqlDataReader reader = cmd.ExecuteReader(); if (reader.Read()) { byte[] data = (byte[])reader["Picture"]; using (MemoryStream stream = new MemoryStream(bytes)) { myImage = new Bitmap(stream); } } } } 但是-如果data因为一些原因不是有效的图片,那么这一行 myImage = new Bitmap(stream); 将会抛出一个异常—无效的参数 只有当你真正看了从数据库里返回到data里的数据-而不是简单的瞄了一眼调试器,你才能注意到是什么原因。。 {byte[21]} [0] 83 [1] 121 [2] 115 [3] 116 [4] 101 [5] 109 [6] 46 [7] 68 [8] 114 [9] 97 [10] 119 [11] 105 [12] 110 [13] 103 [14] 46 ... 它看起来不像是错的,所以它可能就是你想要的。-虽然21字节是一个很大的线索:你的图片可能只有21字节长?那图片可真小。。 但是,上面的是可以读懂的。。稍微练习一下。。每个字节是一个ASCII码。。 "83" is an uppercase 'S' "121" is an lowercase 'y' "115" is an lowercase 's' "116" is an lowercase 't' "101" is an lowercase 'e' "109" is an lowercase 'm' "46" is a '.' "68" is an uppercase 'D' "114" is an lowercase 'r' "97" is an lowercase 'a' "119" is an lowercase 'w' "105" is an lowercase 'i' "110" is an lowercase 'n' "103" is an lowercase 'g' "46" is an lowercase '.' ... 简而言之,你从数据库里得到的数据是一个人类可以读懂的字符串,意思是是 ...

2012-09-27 · 2 min · bystander

C#使用Graphics创建饼图

介绍 这个程序是使用C#中的Graphics来创建一个饼图的,我已经尽我所能写的很好了。如果你有任何建议可以分享给我,这样我也能从中学习。 使用代码 最近我迷上了Graphics类。我仅仅体验了一下Graphics的DrawPie() 和FillPie() 方法。 最为一个简单的Demo,我创建一个有着五个文本框的窗体,一个按钮,一个图片框。一会我就把饼图画在图片框里 在创建一个饼图之前,我们头脑里要有这个意识。我们不能创建一个不符合常规的圆,创建圆我们需要度数信息。 为了转换度数。我们首先把给定的值做个求和。然后得出文本框里所有值的和。然后呢。用每个值除以总值再乘以360度。 代码如下: int i1 = Int32.Parse(textBox1.Text); int i2 = Int32.Parse(textBox2.Text); int i3 = Int32.Parse(textBox3.Text); int i4 = Int32.Parse(textBox4.Text); int i5 = Int32.Parse(textBox5.Text); float total = i1 + i2 + i3 + i4 + i5 ; float deg1 = (i1 / total) * 360; float deg2 = (i2 / total) * 360; float deg3 = (i3 / total) * 360; float deg4 = (i4 / total) * 360; float deg5 = (i5 / total) * 360; 值转换完毕后。我们可以创建Graphics类的实例了。 Graphics graphics = pictureBox1.CreateGraphics(); 然后我们需要创建一个矩形区域,在这个矩形区域里绘制饼图。 Rectangle rect = new Rectangle(0, 0, 150, 150); 前两个参数定义了矩形左上角的坐标,后两个分别定义了举行的宽和高。 为了能够比较清晰的看出饼图各部分的比例。我们需要创建五个笔刷。 Brush brush1 = new SolidBrush(Color.Red); Brush brush2 = new SolidBrush(Color.Blue); Brush brush3 = new SolidBrush(Color.Maroon); Brush brush4 = new SolidBrush(Color.Navy); Brush brush5 = new SolidBrush(Color.YellowGreen); graphics.Clear(pictureBox1.BackColor); 现在我们可以开始创建我们的饼图了。graphics.FillPie();方法接受四个参数 笔刷,用来填充扇形 矩形:饼图将被创建的区域。 开始角度:饼部分的开始角度 覆盖角度:饼部分扫过的角度 一般来说graphics.FillPie();方法并不是创建一个完全的饼图,而是创建饼图的一个扇形部分,我们会创建一系列的扇形最终看起来像是一个饼图。 ...

2012-09-27 · 2 min · bystander