Google [站内搜索]

预览模式: 普通 | 列表
这一原则实际应该取这个名字:“应该创建大小合理而且包含少量公共类型的程序集”。但这太沉长了,所以就以我认为最常见的错误来命名:开发人员总是把所有的东西,除了厨房里水沟以外(译注:夸张说法,kitchen sink可能是个口语词,没能查到是什么意思,所以就直译了。),都放到一个程序集。这不利于重用其中的组件,也不利于系统中小部份的更新。很多以二进制组件形式存在的小程序集可以让这些都变得简单。

然而这个标题对于程序集的内聚来说也很醒目的。程序集的内聚性是指概念单元到单个组件的职责程度。聚合组件可以简单的用一句话概括,你可以从很多.Net的FCL程序集中看到这些。有两个简单的例子:System.Collections程序集就是负责为相关对象的有序集提供数据结构,而System.Windows.Forms程序集则提供Windows控件类的模型。Web form和Windows Form在不同的程序集中,因为它们不相关。你应该用同样的方式,用简单的一句话来描述你的程序集。不要玩花样:一个MyApplication程序集提供了你想要的一切内容。是的,这也是简单的一句,但这也太刁懒了吧,而且你很可能在My2ndApplication(我想你很可能会要重用到其中的一些内容。这里“其中的一些内容”应该放到一个独立的程序集中。)程序集并不须要使用所有的功能。

你不应该只用一个公共类来创建一个程序程序集。应该有一个折衷的方法,如果你太偏激,创建了太多的程序集,你就失去了使用封装的一些好处:首先就是你失去了使用内部类型的机会,内部类型是在一个程序集中与封装(打包)无关的公共类(参见原则33)(译注:简单的说,内部类型就是只能在一个公共的程序集中访问类,程序集以外限制访问)。JIT编译器可以在一个程序集内有很的内联效率,这比起在多程序集中穿梭效率要高得多。这就是说,在一个程序集中放置一些相关的类型对你是有好处的。我们的目标就是为我们的组件创建大小最合适的程序集。这一目标很容易实现,就是一个组件应该只有一个职责。

查看更多...

分类:Web编程 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 6331

Effective C# 原则31:选择小而简单的函数

做为一个有经验的程序员,不管你在使用C#以前是习惯用什么语言的,我们综合了几个可以让你开发出有效代码的实际方法。有些时候,我们在先前的环境中所做的努力在.Net环境中却成了相反的。特别是在你试图手动去优化一些代码时尤其突出。你的这些行为往往会阻止JIT编译器进行最有效的优化。你的以性能为由的额外工作,实际上产生了更慢的代码。你最好还是以你最清楚的方法写代码,其它的让JIT编译器来做。最常见的一个例子就是预先优化,你创建一个很长很复杂的函数,本想用它来避免太多的函数调用,结果会导致很多问题。实际操作时,提升这样一个函数的逻辑到循环体中对.Net程序是有害的。这与你的真实是相反的,让我们来看一些细节。

这一节介绍一个简单的内容,那就是JIT编译器是如何工作的 。.Net运行时调用JIT编译器,用来把由C#编译器生成的IL指令编译成机器代码。这一任务在应用程序的运行期间是分步进行的。JIT并不是在程序一开始就编译整个应用程序,取而代之的是,CLR是一个函数接一个函数的调用JIT编译器。这可以让启动开销最小化到合理的级别,然而不合理的是应用程序保留了大量的代码要在后期进行编译。那些从来不被调用的函数JIT是不会编译它的。你可以通过让JIT把代码分解成更多的小块,从而来最小化大量无关的代码,也就是说小而多的函数比大而少的函数要好。考虑这个人为的例子:

public string BuildMsg( bool takeFirstPath )

查看更多...

分类:Web编程 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 3953
.Net运行环境是语言无关的:开发者可以用不同的.Net语言编写组件。而且在实际开发中往往就是这样的。你创建的程序集必须是与公共语言系统(CLS)是兼容的,这样才能保证其它的开发人员可以用其它的语言来使用你的组件。

CLS的兼容至少在公共命名上要与互用性靠近。CLS规范是一个所有语言都必须支持的最小操作子集。创建一个CLS兼容的程序集,就是说你创建的程序集的公共接口必须受CLS规范的限制。这样其它任何满足CLS规范的语言都可以使用这个组件。然而,这并不是说你的整个程序都要与CLS的C#语言子集相兼容。

为了创建CLS兼容的程序集,你必须遵从两个规则:首先,所以参数以及从公共的和受保护的成员上反回的值都必须是与CLS兼容的。其次,其它不与CLS兼容的公共或者受保护成员必须存在CLS兼容的同意对象。

查看更多...

分类:Web编程 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 4773
你可以用new修饰符来重新定义一个从基类中继承来的非虚成员。你可以这样做,但并不意味着需要这样做。重新定义非虚方法会导致方法含意的混乱。如果两个相关的类是继承关系,那么很多开发人员可能会立即假设两段代码块是做完全相同的事情,而且他们也会这么认为:

object c = MakeObject( );

// Call through MyClass reference:

查看更多...

分类:Web编程 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 5084

Effective C# 原则28:避免转换操作

转换操作是一种等代类型(Substitutability)间操作转换操作。等代类型就是指一个类可以取代另一个类。这可能是件好事:一个派生类的对象可以被它基类的一个对象取代,一个经典的例子就是形状继承。先有一个形状类,然后派生出很多其它的类型:长方形,椭圆形,圆形以及其它。你可以在任何地方用图形状来取代圆形,这就是多态的等代类型。这是正确的,因为圆形就是一个特殊的形状。当你创建一个类时,明确的类型转化是可以自动完成的。正如.Net中类的继承,因为System.Object是所有类型的基类,所以任何类型都可以用System.Obejct来取代。同样的情况,你所创建的任何类型,也应该可以用它所实现的接口来取代,或者用它的基类接口来取代,或者就用基类来取代。不仅如此,C#语言还支持很多其它的转换。

当你为某个类型添加转换操作时,就等于是告诉编译器:你的类型可以被目标类所取代。这可能会引发一些潜在的错误,因为你的类型很可能并不能被目标类型所取代(译注:这里并不是指继承关系上的类型转换,而是C#语言许可我们的另一种转换,请看后文)。它的副作用就是修改了目标类型的状态后可能对原类型根本无效。更糟糕的是,如果你的转换产生了临时对象,那么副作用就是你直接修改了临时对象,而且它会永久丢失在垃圾回收器。总之,使用转换操作应该基于编译时的类型对象,而不是运行时的类型对象。用户可能须要对类型进行多样化的强制转换操作,这样的实际操作可能产生不维护的代码。

你可以使用转换操作把一个未知类型转化为你的类型,这会更加清楚的表现创建新对象的操作(译注:这样的转换是要创建新对象的)。转换操作会在代码中产生难于发现的问题。假设有这样一种情况,你创建了如图3.1那样的类库结构。椭圆和圆都是从形状类继承下来的,尽管你相信椭圆和圆是相关的,但还是决定保留这样的继承关系。这是因为你不想在继承关系中使用非抽象叶子类,这会在从椭圆类上继承圆类时,有一些不好实现的难题存在。然而,你又意识到每一个圆形应该是一个椭圆,另外某些椭圆也可能是圆形。

查看更多...

分类:Web编程 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 4138

Effective C# 原则27:避免使用ICloneable

ICloneable看上去是个不错的主意:为一个类型实现ICloneable接口后就可以支持拷贝了。如果你不想支持拷贝,就不要实现它。

但你的对象并不是在一个“真空”的环境中运行,但考虑到对派生类的些影响,最好还是对ICloneable支持。一但某个类型支持ICloneable, 那么所有的派生类都必须保持一致,也就是所有的成员必须支持ICloneable接口或者提供一种机制支持拷贝。最后,支持深拷贝的对象,在创建设计时如果包含有网络结构的对象,会使拷贝很成问题。ICloneable也觉察到这个问题,在它的官方定义中有说明:它同时支持深拷贝和浅拷贝。浅拷贝是创建一个新的对象,这个新对象对包含当前对象中所有成员变量的拷贝。如果这些成员变量是引用类型的,那么新的对象与源对象包含了同样的引用。而深拷贝则可以很好的拷贝所有成员变量,引用类型也被递归的进行了拷贝。对于像整型这样的内置类型,深拷贝和浅拷贝是一样的结果。哪一种是我们的类型应该支持的呢?这取决于类型本身。但同时在一个类型中混用深拷贝和浅拷贝会导致很多不一致的问题。一但你涉及到ICloneable这个问题,这样的混用就很难解脱了。大多数时候,我们应该完全避免使用ICloneable,让类更简单一些。这样使用和实现都相对简单得多。

任何只以内置类型做为成员的值类型不必支持ICloneable; 用简单的赋值语句对结构的所有值进行拷贝比Clone()要高效得多。Clone()方法必须对返回类型进行装箱,这样才能强制转化成一个System.Object的引用。而调用者还得再用强制转化从箱子中取回这个值。我知道你已经有足够的能力这样做,但不要用Clone()函数来取代赋值语句。

查看更多...

分类:Web编程 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 4195
你的类型应该有一个顺序关系,以便在集合中描述它们如何存储以及排序。.Net框架为你提供了两个接口来描述对象的顺序关系:IComparable 和IComparer。IComparable 为你的类定义了自然顺序,而实现IComparer接口的类可以描述其它可选的顺序。你可以在实现接口时,定义并实现你自己关系操作符(<,>,<=,>=),用于避免在运行时默认比较关系的低效问题。这一原则将讨论如何实现顺序关系,以便.Net框架的核心可以通过你定义的接口对你的类型进行排序。这样用户可以在些操作上得更好的效率。

IComparable接口只有一个方法:CompareTo(),这个方法沿用了传统的C函数库里的strcmp函数的实现原则:如果当前对象比目标对象小,它的返回值小于0;如果相等就返回0;如果当前对象比目标对象大,返回值就大于0。IComparable以System.Object做为参数,因此在使用这个函数时,你须要对运行时的对象进行检测。每次进行比较时,你必须重新解释参数的类型:

public struct Customer : IComparable

查看更多...

分类:Web编程 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 4078
对象的持久是类型的一个核心功能。这是一个在你忽略对它的支持以前,没有人会注意到的基本元素之一。 如果你的类型不能恰当的支持序列化,那么对于把你类的做为基类或者成员的开发人员来说,你会给他们增加很多的工作量。当你的类型不支持序列化时,他们不得不围绕这工作,自己添加实现这个标准的功能。而对于不能访问类的私有成员的开发人来说,恰当的实现你的类型的序列化是不太可能的。如果你的类型不支持序列化,那么对于你的用户来说,想再要实现它是很困难或者根本就不可能的事。

取而代之的是,为你的实际类型添加序列化。对于那些不用承载UI元素,窗口,或者表单的类型来说这是有实际意义的。感觉有额外的工作是没有理由的,.Net的序列化是很简单的,以至于你没有任意的借口说不支持它。在多数情况下,添加Serializable特性就足够了:

[Serializable]

查看更多...

分类:Web编程 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 6296
与命令式编程相比,申明式编程可以用更简单,更清楚的方法来描述软件的行为。申明式编程就是说用申明来定义程序的行为,而不是写一些指令。在C#里,也和其它大多数语言一样,你的大多数程序都是命令式的:在程序中写一个方法来定义行为。在C#中,你在编程时使用特性就是申明式编程。你添加一个特性到类,属性,数据成员,或者是方法上,然后.Net运行时就会为你添加一些行为。这样申明的目的就是简单易用,而且易于阅读和维护。

让我们以一个你已经使用过的例子开始。当你写你的第一个ASP.Net Web服务时,向导会生成这样的代码:

[WebMethod]

查看更多...

分类:Web编程 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 4513
你已经知道,所谓的只读属性就是指调用者无法修改这个属性。不幸运的是,这并不是一直有效的。如果你创建了一个属性,它返回一个引用类型,那么调用者就可以访问这个对象的公共成员,也包括修改这些属性的状态。例如:

public class MyBusinessObject
{
  // Read Only property providing access to a

查看更多...

分类:Web编程 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 4320