技术掘金 - 2013年9月
type: channel
link: http://blog.guqiankun.com/2013/09/
description: 技术掘金,专注于Web互联网开发,分享技术,致力于打造好的技术博客
items: Effective C# 原则24：选择申明式编程而不是命令式编程
items: Effective C# 原则23：避免返回内部类对象的引用
items: Effective C# 原则22：用事件定义对外接口
items: Effective C# 原则21：用委托来表示回调
items: Effective C# 原则20：明辨接口实现和虚函数重载的区别
items: Effective C# 原则19：选择定义和实现接口，而不是继承
items: Effective C# 原则18：实现标准的处理(Dispose)模式
items: 《渝李文集静日书 -- 惊航》
items: 《渝李文集静日书 -- 那一年天使离去》
items: Effective C# 原则17：装箱和拆箱的最小化

Effective C# 原则24：选择申明式编程而不是命令式编程
type: item
date: 2013-09-30T23:27:00+08:00
description: 与命令式编程相比，申明式编程可以用更简单，更清楚的方法来描述软件的行为。申明式编程就是说用申明来定义程序的行为，而不是写一些指令。在C\#里，也和其它大多数语言一样，你的大多数程序都是命令式的：在程序中写一个方法来定义行为。在C\#中，你在编程时使用特性就是申明式编程。你添加一个特性到类，属性，数据成员，或者是方法上，然后.Net运行时就会为你添加一些行为。这样申明的目的就是简单易用，而且易于阅读和维护。让我们以一个你已经使用过的例子开始。当你写你的第一个ASP.NetWeb服务时，向导会生成这样的代码：[WebMethod] public string HelloWorld() { return "Hello World"; } VS.net的Web服务向导添加了[WebMethod]特性到HelloWorld()方法上，这就定义了HelloWorld是一个web方法。ASP.net运行时会为你生成代码来响应这个特性。运行时生成的Web服务描述语言(WSDL)文档，也就是包含了对SOAP进行描述的文档，调用HelloWorld方法。ASP.net也支持运行时发送SOAP请求HelloWorld方法。另外，ASP.net运行时动态的生成HTML面页，这样可以让你在IE里测试你的新Web服务。而这些全部是前面的WebMethod特性所响应的。这个特性申明了你的意图，而且运行时确保它是被支持的。使用特性省了你不少时间，而且错误也少了。这并不是一个神话，ASP.net运行时使用反射来断定类里的哪些方法是web服务，当它们发现这些方法时，ASP.net运行时就添加一些必须的框架代码到这些方法上，从而使任何添加了这些代码的方法成为web方法。[WebMethod]特性只是.Net类库众多特性之一，这些特性可能帮助你更快的创建正确的程序。有一些特性帮助你创建序列化类型(参见原则25)。正如你在原则4里看到的，特性可以控制条件编译。在这种情况以下其它一些情况下，你可以使用申明式编程写出你所要的更快，更少错误的代码。你应该使用.Net框架里自带的一些特性来申明你的意图，这比你自己写要好。因为这样花的时间少，更简单，而且编译器也不会出现错误。如果预置的特性不适合你的需求，你也可以通过定义自己的特性和使用反射来使用申明式编程结构。做为一个例子，你可以创建一个特性，然而关联到代码上，让用户可以使用这个特性来创建默认可以排序的类型。一个例子演示了如何添加这个特性，该特性定义了你想如何在一个客户集合中排序：[DefaultSort( "Name" )] public class Customer { public string Name { get { return _name; } set { _name = value; } } public decimal CurrentBalance { get { return _balance; } } public decimal AccountValue { get { return calculateValueOfAccount(); } } } DefaultSort特性，Nane属性，这就暗示了任何Customer的集合应该以客户名字进行排序。DefaultSort特性不是.Net框架的一部份，为了实现它，你创建一个DefaultSortAttribute类：[AttributeUsage( AttributeTargets.Class | AttributeTargets.Struct )] public class DefaultSortAttribute : System.Attribute { private string _name; public string Name { get { return _name; } set { _name = value; } } public DefaultSortAttribute( string name ) { _name = name; } } 同样，你还必须写一些代码，来对一个集合运行排序，而该集合中的元素是添加了DefaultSort特性的对象。你将用到反射来发现正确的属性，然后比较两个不同对象的属性值。一个好消息是你只用写一次这样的代码。下一步，你要写一个实现了IComparer接口的类。(在原则26中会详细的充分讨论比较。)ICompare有一个CompareTo()方法来比较两个给定类型的对象，把特性放在实现了IComparable的类上，就可以定义排序顺序了。构造函数对于通用的比较，可以发现默认的排序属性标记，而这个标记是基于已经比较过的类型。Compare方法对任何类型的两个对象进行排序，使用默认的排序属性：internal …

Effective C# 原则23：避免返回内部类对象的引用
type: item
date: 2013-09-29T22:49:00+08:00
description: 你已经知道，所谓的只读属性就是指调用者无法修改这个属性。不幸运的是，这并不是一直有效的。如果你创建了一个属性，它返回一个引用类型，那么调用者就可以访问这个对象的公共成员，也包括修改这些属性的状态。例如：public class MyBusinessObject { // Read Only property providing access to a // private data member: private DataSet _ds; public DataSet Data { get { return _ds; } } } // Access the dataset: DataSet ds = bizObj.Data; // Not intended, but allowed: ds.Tables.Clear( ); // Deletes all data tables. 任何MyBusinessObject的公共客户都可以修改你的内部dateset。你创建的属性用来隐藏类的内部数据结构，你提供了方法，让知道该方法的客户熟练的操作数据。因此，你的类可以管理内部状态的任何改变。然而，只读属性对于类的封装来说开了一个后门。当你考虑这些问题时，它并不是一个可读可写属性，而是一个只读属性。欢迎来到一个精彩的基于引用的系统，任何返回引用的成员都会返回一个对象的句柄。你给了调用者一个接口的句柄，因此调用者修改这个对象的某个内部引用时，不再需要通过这个对象。很清楚，你想防止这样的事情发生。你为你的类创建了一个接口，同时希望用户使用这个接口。你不希望用户在不明白你的意图时，访问并修改对象的内部状态。你有四个策略来保护你的内部数据结构不被无意的修改：值类型，恒定类型，接口和包装(模式)。值类型在通过属性访问时，是数据的拷贝。客户对类的拷贝数据所做的任何修改，不会影响到对象的内部状态。客户可以根据需求随意的修改拷贝的数据。这对你的内部状态没有任意影响。恒定类型，例如System.String，也是安全的。你可以返回一个字符串，或者其它恒定类型。恒定类型的安全性告诉你，没有客户可以修改字符串。你的内部状态是安全的。第三个选择就是定义接口，从而充许客户访问内部成员的部份功能(参见原则19)。当你创建一个自己的类时，你可以创建一些设置接口，用来支持对类的子对象进行设置。通过这些接口来暴露一些功能函数，你可以尽可能的减少一些对数据的无意修改。客户可以通过你提供的接口访问类的内部对象，而这个接口并不包含这个类的全部的功能。在DataSet上暴露一个IListsource接口就是这种策略，可以阻止一些有想法的程序员来猜测实现这个接口的对象，以及强制转换。这样做和程序员付出更多的工作以及发现更多的BUG都是自找的(译注：这一句理解可能完全不对，读者可以自行参考原文：Butprogrammers who go to that much work to create bugs get what theydeserve.)。System.Dataset类同时也使用了最后一种策略：包装对象。DataViewManager类提供了一种访问DataSet的方法，而且防止变向的方法来访问DataSeto类：public class MyBusinessObject { // Read Only property providing access to a // private data member: private DataSet _ds; public DataView this[ string tableName ] { get { return _ds.DefaultViewManager. CreateDataView( _ds.Tables[ tableName ] ); } } } // Access the dataset: DataView list = bizObj[ "customers" ]; foreach ( DataRowView r in list ) Console.WriteLine( r[ "name" ] ); DataViewManager创建DataView来访问DataSet里的个别数据表。DataViewManager没有提供任何方法来修改DataSet里的数据表。每一个DataView可以被配置为许可修改个别数据元素，但客户不能修改数据表，或者数据表的列。读/写是默认的，因此客户还是可以添加，修改，或者删除个别的数据条目。在我们开始讨论如何创建一个完全只读的数据视图时以前，让我先简单的了解一下你应该如何响应公共用户的修改。这是很重要的，因为你可能经常要暴露一个DataView给UI控件，这样用户就可以编辑数据(参见原则…

Effective C# 原则22：用事件定义对外接口
type: item
date: 2013-09-28T23:21:00+08:00
description: 可以用事件给你的类型定义一些外部接口。事件是基于委托的，因为委托可以提供类型安全的函数签名到事件句柄上。加上大多数委托的例子都是使用事件来说明的，以至于开发人员一开始都认为委托与事件是一回事。在原则21里，我已经展示了一些不在事件上使用委托的例子。在你的类型与其它多个客户进行通信时，为了完成它们的行为，你必须引发事件。一个简单的例子，你正在做一个日志类，就像一个信息发布机一样在应用程序里发布所有的消息。它接受所有从程序源发布的消息，并且把这些消息发布到感兴趣的听众那里。这些听众可以是控制台，数据库，系统日志，或者是其它的机制。就可以定义一个像下面这样的类，当消息到达时来引发事件：public class LoggerEventArgs : EventArgs { public readonly string Message; public readonly int Priority; public LoggerEventArgs ( int p, string m ) { Priority = p; Message = m; } } // Define the signature for the event handler: public delegate void AddMessageEventHandler(object sender, LoggerEventArgs msg); public class Logger { static Logger( ) { _theOnly = new Logger( ); } private Logger( ) { } private static Logger _theOnly = null; public Logger Singleton { get { return _theOnly; } } // Define the event: public event AddMessageEventHandler Log; // add a message, and log it. public void AddMsg ( int priority, string msg ) { // This idiom discussed below. AddMessageEventHandler l = Log; if ( l != null ) l ( null, new LoggerEventArgs( priority, msg ) ); } }AddMsg方法演示了一个恰当的方法来引发事件。临时的日志句柄变量是很重要的，它可以确保在各种多线程的情况下，日志句柄也是安全的。如果没有这个引用的COPY，用户就有可能在if检测语句和正式执行事件句柄之间移除事件句柄。有了引用COPY，这样的事情就不会发生了。我还定义了一个LoggerEventArgs来保存事件和消息的优先级。委托定义了事件句柄的签名。而在Logger类的内部，事件字段定义了事件的句柄。编译器会认为事件是公共的字段，而且会为你添加Add和Remove两个操作。生成的代码与你这样手写的是一样的：public class Logger { private AddMessageEventHandler _Log; public event AddMessageEventHandler Log { add { _Log = _Log + value; } remove { _Log = _Log - value; } } public void AddMsg (int priority, string msg) { AddMessageEventHandler l = _Log; if (l != null) l (null, new LoggerEventArgs (priority, msg)); } } }C\#编译器创建Add和Remove操作来访问事件。看到了吗，公共的事件定义语言很简洁，易于阅读和维护，而且更准确。当你在类中添加一个事件时，你就让编译器可以创建添加和移除属性。你可以，而且也应该，在有原则要强制添加时自己手动的写这些句柄。事件不必知道可能成为监听者的任何资料，下面这个类自动把所有的消息发送到标准的错误设备(控制台)上：class ConsoleLogger { static ConsoleLogger() { logger.Log += new AddMessageEventHandler( Logger_Log ); } private static void Logger_Log( object sender, LoggerEventArgs msg ) { Console.Error.WriteLine(…

Effective C# 原则21：用委托来表示回调
type: item
date: 2013-09-27T21:52:00+08:00
description: 我：“儿子，到院子里除草去，我要看会书。” 斯科特：“爸，我已经打扫过院子了。” 斯科特：“爸，我已经把草放在除草机上了。” 斯科特：“爸，除草机不能启动了。” 我：“让我来启动它。” 斯科特：“爸，我做好了。”这个简单的交互展示了回调。我给了我儿子一个任务，并且他可以报告状态来(重复的)打断我。而当我在等待他完成任务的每一个部份时，我不用阻塞我自己的进程。他可以在有重要(或者事件)状态报告时，可以定时的打断我，或者向我询求帮助。回调就是用于异步的提供服务器与客户之间的信息反馈。它们可能在多线程中，或者可能是简单的提供一个同步更新点。在C\#里是用委托来表示回调的。委托提供了一个类型安全的回调定义。尽管委托大多数是为事件使用的，但这不应该是C\#语言中唯一使用这一功能的地方。任何时候，如果你想在两个类之间进行通信，而你又期望比使用接口有更少的偶合性，那么委托是你正确的选择。委托可以让你在运行确定(回调)目标并且通知用户。委托就是包含了某些方法的引用。这些方法可以是静态方法，也可以是实例方法。使用委托，你可以在运行时确定与一个或者多个客户对象进行交互。多播委托包含了添加在这个委托上的所有单个函数调用。有两点要注意的：它不是异常安全的，并且返回值总是委托上最后一个函数调用后返回的值。在多播委托调用的内部，每一个目标都会成功的调用。委托不会捕获任何的异常，也就是说，在委托链中抛出的任何异常都会终止委托链的继续调用。在返回值上也存在一个简单的问题。你可以定义委托有返回值或者是void。你可能会写一个回调函数来检测用户的异常中断：public delegate bool ContinueProcessing(); public void LengthyOperation( ContinueProcessing pred ) { foreach( ComplicatedClass cl in _container ) { cl.DoLengthyOperation(); // Check for user abort: if (false == pred()) return; } } 在单委托上这是工作的，但在多播委托上却是有问题的：ContinueProcessing cp = new ContinueProcessing (CheckWithUser); cp += new ContinueProcessing(CheckWithSystem); c.LengthyOperation( cp ); 从委托的调用上返回的值，其实是它的最后一个函数的调用上返回的值。其它所有的的返回值都被忽略。即，从CheckWithUser()返回的断言被忽略。你可以自己手动的设置两个委托来调用两个函数。你所创建的每一个委托都包含有一个委托链。直接检测这个委托链，并自己调用每一个委托：public delegate bool ContinueProcessing(); public void LengthyOperation( ContinueProcessing pred ) { bool bContinue = true; foreach( ComplicatedClass cl in _container ) { cl.DoLengthyOperation(); foreach( ContinueProcessing pr in pred.GetInvocationList( )) bContinue &= pr(); if (false == bContinue) return; } } 这时，我已经定义好了程序的语义，因此委托链上的每个委托必须返回真以后，才能继续调用。委托为运行时回调提供了最好的方法，用户简单的实现用户对类的需求。你可以在运行时确定委托的目标。你可以支持多个用户目标，这样，用户的回调就可以用.Net里的委托实现了。

Effective C# 原则20：明辨接口实现和虚函数重载的区别
type: item
date: 2013-09-26T21:14:00+08:00
description: 粗略的看一下，感觉实现接口和虚函数重载是一样的。你定义了一些对象，但是这些对象是在另一个类型里申明的。你被第一感觉骗了，实现接口与虚函数重载是完全不同的。在接口里定义的成员默认情况下，是根本不存在实际内容的。派生类不能重载基类中的接口成员。接口可以隐式的实现，就是把它们从类的公共接口中隐藏。它们的概念是不同的而且使用也是不同的。但你可以这样的实现接口：让你的派生类可以修改你的实现。你只用对派生类做一个Hook就行了。(译注：相信写过C++程序的人就知道hook是什么意思，而且我也实在想不到把hook译成什么比较好，所以就直接用hook这个原词了，就像bug一样。)为了展示它们的不同之处，试着做一个简单的接口以及在一个类中实现它：interface IMsg { void Message(); } public class MyClass : IMsg { public void Message() { Console.WriteLine( "MyClass" ); } } Message()方法是MyClass的公共接口，Message同样可以用一个接口指针IMsg来访问。现在让我们来一点繁杂的，添加一个派生类：public class MyDerivedClass : MyClass { public new void Message() { Console.WriteLine( "MyDerivedClass" ); } } 注意到，我添加了一个关键字new在Message方法上，用于区别前面的一个Message(参见原则29)。MyClass.Message()不是虚函数，派生类可以不提供重载版本。MyDerived类创建了一个新的Message方法，但这个方法并不是重载MyClass.Message:它隐藏了原来的方法。而且，MyClass.Message还是可以通过IMsg的引用来访问：MyDerivedClass d = new MyDerivedClass( ); d.Message( ); // prints "MyDerivedClass". IMsg m = d as IMsg; m.Message( ); // prints "MyClass" 接口方法不是虚的，当你实现一个接口时，你就要在详细的相关类型中申明具体的实现内容。但你可能想要创建接口，在基类中实现这些接口而且在派生类中修改它们的行为。这是可以办法到的。你有两个选择，如果不访问基类，你可以在派生类中重新实现这个接口：public class MyDerivedClass : MyClass, IMsg { public new void Message() { Console.WriteLine( "MyDerivedClass" ); } } 添加的IMsg让你的派生类的行为发生了改变，以至IMsg.Message现在是在派生类上使用的：MyDerivedClass d = new MyDerivedClass( ); d.Message( ); // prints "MyDerivedClass". IMsg m = d as IMsg; m.Message( ); // prints "MyDerivedClass" 派生类上还是须要在MyDerivedClass.Message()方法上添加关键字new，这还是有一点隐患(参见原则29)。基类还是可以通过接口引用来访问：MyDerivedClass d = new MyDerivedClass( ); d.Message( ); // prints "MyDerivedClass". IMsg m = d as IMsg; m.Message( ); // prints "MyDerivedClass" MyClass b = d; b.Message( ); // prints "MyClass" 唯一可以修正这个问题的方法是修改基类，把接口的申明修改为虚函数：public class MyClass : IMsg { public virtual void Message() { Console.WriteLine( "MyClass" ); } } public class MyDerivedClass : MyClass { public override void Message() { Console.WriteLine( "MyDerivedClass" ); } } MyDerivedClass以及其它所有从MyClass派生的类可以申明它们自己的Message()方法。这个重载的版本每次都会调用：通过MyDerivedClass的引用，通过IMsg接口的引用，或者直接通…

Effective C# 原则19：选择定义和实现接口，而不是继承
type: item
date: 2013-09-25T22:51:00+08:00
description: 抽象类在类的继承中提供了一个常规的“祖先”。一个接口描述了一个可以被其它类型实现的原子级泛型功能。各有千秋，却也不尽相同。接口是一种合约式设计：一个类型实现了某个接口的类型，就必须实现某些期望的方法。抽象类则是为一个相关类的集合提供常规的抽象方法。这些都是老套的东西了：它是这样的，继承就是说它是某物(isa,)，而接口就是说它有某个功能(behaves like.)!这些陈词滥调已经说了好久了，因为它们提供了说明，同时在两个结构上描述它们的不同：基类是描述对象是什么，接口描述对象有某种行为。接口描述了一组功能集合，或者是一个合约。你可以在接口里创建任何的占位元素(placeholder，译注：就是指先定义，后面再实现的一些内容)：方法，属性，索引器以及事件。任何实现类型这个接口的类型必须为接口里的每个元素提供具体的内容。你必须实现所有的方法，提供全部属性访问器，索引器，以及定义接口里的所有事件。你在接口里标记并且构造了可重用的行为。你可以把接口当成参数或者返回值，你也可以有更多的机会重用代码，因为不同的类型可以实现相同的接口。更多的是，比起从你创建的基类派生，开发人员可以更容易的实现接口。(译注：不见得!)你不能在接口里提供任何成员的具体实现，无论是什么，接口里面都不能实现。并且接口也不能包含任何具体的数据成员。你是在定义一个合约，所有实现接口的类型都应该实现的合约。抽象的基类可以为派生类提供一些具体的实现，另外也描述了一些公共的行为。你可以更详细的说明数据成员，具体方法，实现虚函数，属性，事件以及索引器。一个基类可以只提供部份方法的实现，从而只提供一些公共的可重用的具体实现。抽象类的元素可以是虚的，抽象的，或者是非虚的。一个抽象类可以为具体的行为提供一个可行的实现，而接口则不行。重用这些实现还有另一个好处：如果你在基类中添加一个方法，所有派生类会自动隐式的增加了这个方法。这就是说，基类提供了一个有效的方法，可以随时扩展几个(派生)类型的行为：就是向基类添加并实现方法，所有派生类会立即具有这些行为。而向一个接口添加一个方法，所会破坏所有原先实现了这个接口的类。这些类不会包含新的方法，而且再也通不过编译。所有的实现者都必须更新，要添加新的方法。这两个模式可以混合并重用一些实现代码，同时还可以实现多个接口。System.Collections.CollectionBase就是这样的一个例子，它个类提供了一个基类。你可以用这个基类你的客户提供一些.Net缺少的安全集合。例如，它已经为你实现了几个接口：IList,ICollection,和IEnumerable。另外，它提供了一个受保护的方法，你可以重载它，从而为不同的使用情况提供自己定义的行为。IList接口包含向集合中添加新对象的Insert()方法。想自己更好的提供一个Insert方法的实现，你可以通过重载CollectionBase类的OnInsert()或OnInsertCcomplete()虚方法来处理这些事件：public class IntList : System.Collections.CollectionBase { protected override void OnInsert( int index, object value ) { try { int newValue = System.Convert.ToInt32( value ); Console.WriteLine( "Inserting {0} at position {1}", index.ToString(), value.ToString()); Console.WriteLine( "List Contains {0} items", this.List.Count.ToString()); } catch( FormatException e ) { throw new ArgumentException( "Argument Type not an integer", "value", e ); } } protected override void OnInsertComplete( int index, object value ) { Console.WriteLine( "Inserted {0} at position {1}", index.ToString( ), value.ToString( )); Console.WriteLine( "List Contains {0} items", this.List.Count.ToString( ) ); } } public class MainProgram { public static void Main() {…

Effective C# 原则18：实现标准的处理(Dispose)模式
type: item
date: 2013-09-24T23:02:00+08:00
description: 我们已经讨论过，处理一个占用了非托管资源对象是很重要的。现在是时候来讨论如何写代码来管理这些类占用的非内存资源了。一个标准的模式就是利用.Net框架提供的方法处理非内存资源。你的用户也希望你遵守这个标准的模式。也就是通过实现IDisposable接口来释放非托管的资源，当然是在用户记得调用它的时候，但如果用户忘记了，析构函数也会被动的执行。它是和垃圾回收器一起工作的，确保在一些必要时候，你的对象只会受到因析构函数而造成的性能损失。这正是管理非托管资源的好方法，因此有必要彻底的弄明白它。处在类继承关系中顶层的基类应该实现IDisposable接口来释放资源。这个类型也应该添加一个析构函数，做为最后的被动机制。这两个方法都应该是用虚方法来释放资源，这样可以让它的派生类重载这个函数来释放它们自己的资源。派生类只有在它自己须要释放资源时才重载这个函数，并且一定要记得调用基类的方法。开始时，如果你的类使用了非内存资源，则一定得有一个析构函数。你不能指望你的用户总是记得调用Dispose方法，否则当他们忘记时，你会丢失一些资源。这或许是因为他们没有调用Dispose的错误，但你也有责任。唯一可以确保非内存资源可以恰当释放的方法就是创建一个析构函数。所以，添加一个析构函数吧!当垃圾回收器运行时，它会直接从内存中移除不用析构的垃圾对象。而其它有析构函数的对象还保留在内存中。这些对象被添加到一个析构队列中，垃圾回收器会起动一个线程专门来析构这些对象。当析构线程完成它的工作后，这些垃圾对象就可以从内存中移除了。就是说，须要析构的对象比不须要析构的对象在内存中待的时间要长。但你没得选择。如果你是采用的这种被动模式，当你的类型占用非托管资源时，你就必须写一个析构函数。但目前你还不用担心性能问题，下一步就保证你的用户使用更加简单，而且可以避免因为析构函数而造成的性能损失。实现IDisposable接口是一个标准的模式来告诉用户和进行时系统：你的对象占有资源而且必须及时的释放。IDisposable接口只有一个方法：public interface IDisposable { void Dispose(); } 实现IDisposable.Dispose()方法有责任完成下面的任务：1、感知所有的非托管资源。 2、感知所有的托管资源(包括卸载一些事件)。3、设置一个安全的标记来标识对象已经被处理。如果在已经处理过的对象上调用任何方法时，你可以检验这个标记并且抛出一个ObjectDisposed的异常。4、阻止析构。你要调用GC.SuppressFinalize(this)来完成最后的工作。通过实现IDisposable接口，你写成了两件事：第一就是提供了一个机制来及时的释放所有占用的托管资源(译注：这里就是指托管资源，当实现了这个接口后，可以通过调用Dispose来立即释放托管资源)，另一个就是你提供了一个标准的模式让用户来释放非托管资源。这是十分重要的，当你在你的类型上实现了IDisposable接口以后，用户就可以避免析构时的损失。你的类就成了.Net社区中表现相当良好的成员。但在你创建的机制中还是存在一些漏洞。如何让一个派生类清理自己的资源，同时还可以让基类很好的再做资源清理呢？(译注：因为调用Dispose方法时，必须调用基类的Dispose，当然是在基类有这个方法时。但前面说过，我们只有一个标记来标识对象是否处理过，不管先调用那个，总得有一个方法不能处理这个标记，而这就存在隐患)如果基类重载了析构函数，或者自己添加实现了IDisposable接口，而这些方法又都是必须调用基类的方法的；否则，基类无法恰当的释放资源。同样，析构和处理共享了一些相同的职责：几乎可以肯定你是复制了析构方法和处理方法之间的代码。正如你会在原则26中学到的，重载接口的方法根本没有如你所期望的那样工作。Dispose标准模式中的第三个方法，通过一个受保护的辅助性虚函数，制造出它们的常规任务并且挂接到派生类来释放资源。基类包含接口的核心代码，派生类提供的Dispose()虚函数或者析构函数来负责清理资源：protected virtual void Dispose( bool isDisposing ); 重载的方法同时完成析构和处理必须提供的任务，又因为它是虚函数，它为所有的派生类提供函数入口点。派生类可以重载这个函数，提供恰当的实现来释放它自己的资源，并且调用基类的函数。当isDisposing为true时你可能同时清理托管资源和非托管资源，当isDisposing为false时你只能清理非托管资源。两种情况下，都可以调用基类的Dispose(bool)方法让它去清理它自己的资源。当你实现这样的模式时，这里有一个简单的例子。MyResourceHog类展示了IDispos…

《渝李文集静日书 -- 惊航》
type: item
date: 2013-09-23T22:19:00+08:00
description: 发布于：2006-9-6 15:04:261因为尹崇民，她想起这段惊航。这夜她赤脚坐在冰冷的木质地板上，窗外有雷鸣电闪，崇民正安好地睡在她身边。她听到了崇民的呼吸，安定平稳。她闭上眼睛，就有层叠影像纷至沓来。像是一场深海远航，记忆的末梢总会隐隐触及那样一些暗礁。是时，她听到时间的航船破浪前行的声音，听到暗礁硌到船底，发出了让人心碎的咔咔声。于是她爬起来，回到崇民身边，抱住了他暖而有力的臂膀。在那里，勇敢的大副带她离开，奔赴一段面向朝阳的旅途。她看到一朵暗礁深处的浪花在身后绽放，白浪滚滚，逐渐隐没。2十五岁的时候，衣砚疯狂地想穿高跟鞋。那时她与外婆住在城南小镇上，一座老旧的木质房子，楼下是一顺的干杂店，卖五毛钱的头花和一块钱的香烟，房子侧面有一道回旋楼梯，木质扶手木质踏板，人踩上去噔噔响。她喜欢买上一串蘸过甜酱的香干，站在楼梯下慢慢咬。街景热闹，有骑破烂脚踏车下班买菜的，有推着小推车卖烙饼水果的，还有东家的猫西家的狗，间或有一场乱糟糟的撕咬打斗，一条小街沸成一锅粥。这些热闹她并不留心，惟一能记得的，是临街小吃店的老板娘。每天傍晚，老板娘会踩着红彤彤的高跟鞋从干杂店前面过，藕臂上偎着闪亮的小拎包，买二两话梅或一袋瓜子，顾盼生姿地走出小街去。有一次她在楼上看到老板娘，穿胭脂色连衣裙，似一团红云从路口飘来。她赶紧下楼，鞋也没顾上穿好。老板娘买好瓜子，看她站在一旁瞧，以为她想吃，走过来笑眯眯地掏一捧往她手里放。这时候外婆在楼上好大声叫她的名字：衣砚，还不快上来写作业。衣砚没敢接，转身往楼上跑。噔噔噔，脚步重得震落了楼板上的灰尘。等她转头看的时候，老板娘还站在原地，笑眯眯地望着她，然后两只手指捻一颗瓜子放到嘴里，噗，瓜子皮落到台阶上。之后她听外婆说，老板娘，风骚。风骚这两个字在十五岁的衣砚看来，不具备任何贬义。尽管外婆一边剪线头一边用不屑的语气说，这回不晓得又和哪个男人勾搭上了。不过勾搭这两个字衣砚倒是懂得。那对卖干杂的小夫妻以及贩菜的老两口，都不算是勾搭。年少的衣砚想，勾搭这种事，必须是老板娘这样漂亮的女人才能做到。只是外婆骂老板娘风骚，有时又会叹气，说那女人可怜。衣砚就总爱追问，怎么可怜了？外婆说，她大概新婚守寡，儿子生下来也寄养别处，因为招了些骂名，这么多年来，也始终没有哪个正经男人肯同她好。摇摇头，外婆又说，一个女人，很快就老了。这是衣砚关于十五岁最深刻的记忆。十五岁，她做梦都想像老板娘那样，穿着红色高跟鞋，款摆着柳腰走过小街。二两话梅或一包瓜子拎在她手里，十指上的那点红一闪一闪，唇角有痣隐现，笑一笑，醉落一片桃花。后来有一天，妈妈回小镇看她和外婆。那夜她睡在木楼小间的钢丝床上，听见妈妈在和外婆说什么重点什么转学。她并不很在意。亮白的月光从木楼走廊外流淌过来，翻越黝黑的廊柱和雕花的栏杆，在木质地板上展平。那里躺着妈妈宝蓝色的缎面高跟鞋，一颗小小的水钻发出了冷硬的光。她盯着那点亮光，一直熬到妈妈和外婆的声音低下去，终于听不见。然后她爬起来，光着脚丫蹑手蹑脚走过去，偷偷抱起那双鞋。深秋的小街万籁俱寂，没有人，也没有灯。她小小的白瓷般的双脚放在空荡荡的高跟鞋里，像幽蓝花瓶里栽种着两朵莲花。一步，两步，笃笃的敲击声响起，她是高傲的公主，在月光下接受着空巷来风的注视。那种瞬间而至的成长，在月色中，在空巷里，仓猝而忧伤。3衣砚回小镇是在十七岁，考上大学以后。那一年的小镇对她来说，是所有悲伤的发源。外婆在春天过世了。因为怕影响她高考，妈妈甚至不肯把这个消息告诉她。衣砚很后悔。她还记得外婆坐在书桌边借着台灯的光线挑脚边的样子——微微张着嘴，针凑到眼前，两只手指捏着线头抖抖嗦嗦地试探，啊一声，穿过去了，脸上露出如释重负的表情，然后抬头看看她的课本说：专心。衣砚想，如果能早点回来，大约还能在月圆的夜晚，见到舍不得走掉的外婆。老人们都说，如果一个人心有留恋，是能在死后一段时间里，回来完成心愿的。外婆最大的心愿就是能再看她一眼吧，那么多年，她们相依为命，外婆走的时候，一定有很多很多话想要对她说。可她的归来毕竟是晚了。那张菲薄的通知书上有她的名字，不过外婆走得太远，已经看不见了。这时候的外婆，想是已经喝了孟婆汤，过了奈何桥，忘记有她这个小人儿还等在这里。她想起能为外婆做的好多事，不过等她明白子欲养而亲不待的时候，外婆已经不见了。她去给外婆的坟上烧纸钱，在田边听人讲起老板娘的事。老板娘死了，在镇郊的一大片葵花地里。盛夏的葵花很灿烂，金黄里带着点点青，每天都是笑脸。只是老板娘的红色高跟鞋抛落在地里，大片大片伏倒的葵花盖住了她赤裸的身体。静寂的小镇投了一颗弹，黑压压的人群包围了那片金黄得刺眼的农田。人群散开的时候警犬来了，在那具不再鲜活的肉体上嗅嗅，发出呜呜的声音。后来有人说老板娘是自杀，约略是为什么人动了真情，对方却是个有妇之夫，不肯给她好的名分，不肯娶她；不…

《渝李文集静日书 -- 那一年天使离去》
type: item
date: 2013-09-23T22:16:00+08:00
description: 发布于：2006-9-2 14:36:17生命原是那样的忧伤和残酷，我们在呼啸而过的记忆中开始奔跑与追逐。大风中亲爱的你们有没有听到，那女孩挥动着翅膀在轻声说——其实，每一位天使都终将离去。12003年2月初，我再次见到这个叫流岚的女孩。当她异常温柔地对我说，请把你的病历卡拿给我，我知道，她已经不记得我。她拿着我的病历卡，目光在我的名字上停顿了几秒钟，然后她问：你叫严戈？是的，我说。我的心里还有着极大的期待，我迫不及待地肯定，然后用热切的眼光看着她。哦。她什么也没说，只是点点头，用黑色签字笔在那下面的空白处飞快地写了两行字，然后递给我，严戈，你进去吧。在那一瞬间，我觉得身体内有什么东西被彻底绷裂。我感觉不到自己还是完整的一体，好像所有的器官都在同一时间停止了运转，分崩离析，只剩胸膛以左的某处还在异常剧烈地跳动。我相信她听到了这不寻常的声音，我转回头时，分明看见她抬头看了我一眼。这一眼，便是我一厢情愿，也愿意相信它是意味深长的。只是她的嘴唇始终紧抿着，吝于再说出一个字。我就在她沉默的注视中，仓皇而逃。2流岚的出现和消失，就像一场梦的始末。开始，我和女友路枚是众人口中的郎才女貌，天作之合。新千年伊始，我还在外贸公司做部门主管，路枚的事业也刚起步，非常辛苦。所幸工作顺利，恋爱甜蜜，年中在三环买了房子。我同路枚商量，等年底房子完工装修好就结婚。于是路枚每天下班总是拉着我往工地跑，穿着套装高跟鞋一扭一扭地往钢筋水泥缝里钻。好几次被守工地的大爷恶狠狠地骂，她也不生气，只是笑嘻嘻地望着我，跟孩子一般聒噪地问来问去，你看卧室涂什么颜色呢？你看地板用那种牌子呢？2001年春天，温暖的三月，我和路枚拿到房子钥匙，第一次去看我们的家。空荡荡的客厅和雪白的墙壁，但一切很美妙。她站在二十一层楼高的露台上，衬着微蓝天空洁白流云，把身子斜斜地向外倾，张开双臂用尽全力大声喊——这是严戈路枚的家。以后的日子里，我们总是在下班后一起牵手去屈臣氏看家具。我一直以为最迟九月，我和路枚的婚礼就能定下来。可到五月底房子布置得差不多时，路枚突然跟我说要到另一个城市去拓展市场。为这事我们起了前所未有的争执，路枚的父母还专门北上，但她去意已决。临别那晚，我们躺在房间的床上听音乐时她说，严戈，我只是想让自己足够独立与坚强，在你累了的时候，能够撑起这个家的重量。3路枚离开我，留下满满的寂寞。每天下班后我常常一个人躺在客厅冰冷的沙发上，开着电视放着音乐毫无节制地喝酒，胃病就在这个时候复发。我第一次上医院，是在2001年路枚离开后的十月里。北方的天气已经有微微的凉意，医院回廊上的风很大。下午，阳光把走廊尽头窗棂的影子投在光滑的地板上，拉得很长。我从医院出来，遇上父亲在医院工作时的同事常青。很长时间不见面，常青忍不住埋怨我，来看病也不事先打声招呼，我好帮你安排呀。正说着，门里突然冲出一个女孩，冲着常青嚷，常主任，快，快去看看严歌。严戈？我当场愣在那里。常青一边匆匆往大门里走一边甩给我一句，她男朋友也叫严歌。这是我第一次见到流岚时的情形。她的五官异常白皙精致，仿佛易碎的玻璃，眼睛红肿，显然刚刚哭过。她的身体在北方的十月里看起来瘦得不像话，毛衣领口露出的锁骨冷冷地扎人。她的声音颤抖但是神情坚强，跟常青进门的时候，她特意转身问我，你也叫严歌吗？我说对，严肃的严，戈壁的戈。然后她深深地看我一眼，扯出一个有点凄凉意味的微笑，转身匆匆离去。4十一月底，我为公司签成一份为数颇巨的合同。周末客户过来，老总做东设宴。那日喝了很多酒，心情非常愉快，给路枚打电话，被吹捧上了天，一时间情绪高涨，便乘夜到四环一个僻静的小酒吧里坐坐。那地方叫危城。路枚最初离开的日子，我经常一个人开车过来呆坐半日，听听蓝调，或者只是静坐。那夜人尤其少。入夜开始下第一场雪，不很大，但是大风乍起且气势汹汹。我在吧台同酒保胡侃的时候，有人推门进来。我没想到还会见到流岚，也没想到她会在这种天气这种环境里出现。她穿着驼色长大衣和同色的小羊皮靴，围着厚厚围巾，两只手揣在大衣口袋里，还是很瘦，不胜风寒的样子，进门的时候，微微喘口气，带来午夜十二点的雪意。她看见我，愣了愣，似乎也很意外，然后走过来打招呼说你好严戈，没想到会在这里遇见你。她在我身边坐下来，姿势娴熟地点上香烟，和当日我初见的她判若两人。我忍不住倾身过去，轻叩两下桌子说，流岚，这样抽烟，会被尼古丁杀死的。她狠狠吸一口，转过头对我笑。她的气息萦绕在我鼻尖，我知道在来这里之前，她已经喝过不少酒。我终于还是问了。你男朋友，他的病情好些了么？她并不看我，低下头把手边大杯透蓝液体一饮而尽。然后她把脸深深埋进胳膊里，很久很久之后，有哭声断断续续传了出来。最后她醉了。她手里还夹着那支青烟袅袅的烟。酒保小五过来，充满怜惜地拿开她手中香烟。他说，已经没有任何希望了，她为他奔走两年多，还是没有办法，即使她…

Effective C# 原则17：装箱和拆箱的最小化
type: item
date: 2013-09-23T22:14:00+08:00
description: 值类型是数据的容器，它们不具备多太性。另一方面就是说，.Net框架被设计成单一继承的引用类型，System.Object，在整个继承关系中做为根对象存在。设计这两种类型的目的是截然不同的，.Net框架使用了装箱与拆箱来链接两种不同类型的数据。装箱是把一个值类型数据放置在一个无类型的引用对象上，从而使一个值类型在须要时可以当成引用类型来使用。拆箱则是额外的从“箱”上拷贝一份值类型数据。装箱和拆箱可以让你在须要使用System.Object对象的地方使用值类型数据。但装箱与拆箱操作却是性能的强盗，在些时候装箱与拆箱会产生一些临时对象，它会导致程序存在一些隐藏的BUG。应该尽可能的避免使用装箱与拆箱。装箱可以把一个值类型数据转化也一个引用类型，一个新的引用对象在堆上创建，它就是这个“箱子”，值类型的数据就在这个引用类型中存储了一份拷贝。参见图2.3，演示了装箱的对象是如何访问和存储的。箱子中包含一份这个值类型对象的拷贝，并且复制实现了已经装箱对象的接口。当你想从这个箱子中取回任何内容时，一个值类型数据的拷贝会被创建并返回。这就是装箱与拆箱的关键性概念：对象的一个拷贝存放到箱子中，而不管何时你再访问这个箱子时，另一个拷贝又会被创建。![EffectiveC\#](http://cdn.guqiankun.com/img/201309/20130923220358265625.jpg)\图2.3，值类型数据在箱子中。把一个值类型数据转化成一个System.Object的引用，一个无名的引用类型会被创建。值类型的数据就存储在这个无名的引用对象中，所有的访问方法都要通过这个箱子才能到达值类型数据存储的地方。最阴险的地方是这个装箱与拆箱很多时候是自动完成的!当你在任何一个期望类型是System.Object的地方使用值类型数据时，编译器会生成装箱与拆箱的语句。另外，当你通过一个接口指针来访问值类型数据时，装箱与拆箱也会发生。当你装箱时不会得到任何警告，即使是最简单的语句也一样。例如下面这个：Console.WriteLine("A few numbers:{0}, {1}, {2}", 25, 32, 50); 使用重载的Console.WriteLine函数须要一个System.Object类型的数组引用，整型是值类型，所以必须装箱后才能传给重载的WriteLine方法。唯一可以强制这三个整数成为System.Object对象的方法就是把它们装箱。另外，在WriteLine内部，通过调用箱子对象上的ToString()方法来到达箱子内部。某种意义上讲，你生成了这样的结构：int i =25; object o = i; // box Console.WriteLine(o.ToString()); 在WriteLine内部，下面的执行了下面的代码：object o; int i = ( int )o; // unbox string output = i.ToString( ); 你可能自己从来不会写这样的代码，但是，却让编译器自动从一个指定的类型转化为System.Object，这确实是你做的。编译器只是想试着帮助你，它想让你成功(调用函数)，它也很乐意在必要时候为你生成装箱和拆箱语句，从而把一个值类型数据转化成System.Object的实例。为了避免这么挑剔的惩罚，在使用它们来调用WriteLine之前，你自己应该把你的类型转化成字符串的实例。Console.WriteLine("A few numbers:{0}, {1}, {2}", 25.ToString(), 32.ToString(), 50.ToString()); (译注：注意，在自己调用ToString方法时，还是会在堆上创建一个引用实例，但它的好处是不用拆箱，因为对象已经是一个引用类型了。)这段代码使用已知的整数类型，而且值类型再也不会隐式的转化为System.Object类型。这个常见的例子展示了避免装箱的第一个规则：注意隐式的转化为System.Object，如果可以避免，值类型不应该被System.Object代替。另一个常见情况就是，在使用.Net1.x的集合时，你可能无意的把一个值类型转化成System.Object类型。任何时候，当你添加一个值类型数据到集合时中，你就创建了一个箱子。任何时候从集合中移出一个对象时，你得到的是箱子里的一个拷贝。从箱子里取一个对象时，你总是要创建一个拷贝。这会在应用程序中产生一些隐藏的BUG。编译器是不会帮你查找这些BUG的。这都是装箱惹的祸。让我们开始创建一个简单的结构，可以修改其中一个字段，并且把它的一些实例对象放到一个集合中：public struct Person { private string _Name; pu…