
编写高质量的C#代码
文章平均质量分 65
侯文成
细节决定成败!!!
我爬行在前往Microsoft MVP的道路上!
励志成为最优秀系统架构师的程序员!
展开
-
建议95:避免在构造方法中调用虚成员
建议95:避免在构造方法中调用虚成员在构造方法中调用虚方法会带来一些意想不到的错误,虽然这种方法不常见,但还是需要注意这类陷阱。 static void Main() { American american = new American(); Console.ReadKey(); }转载 2016-09-09 09:59:21 · 318 阅读 · 0 评论 -
建议96:成员应优先考虑公开基类型或接口
建议96:成员应优先考虑公开基类型或接口类型成员如果优先考虑公开及类型或接口,那么会让类型支持更多的应用场合。FCL中最典型的例子是集合的功能操作。集合根据功能划分有多种类型,比如List、Dictionary、HashSet等。以一个最简单的操作Empty(清空集合)为例。该功能要求我们删除集合中的所有元素,然后返回一个干净的集合。如果不返回基类型或接口的话,则要求我们为每一个集转载 2016-09-09 09:59:36 · 275 阅读 · 0 评论 -
建议97:优先考虑将基类型或接口作为参数传递
建议97:优先考虑将基类型或接口作为参数传递除了公开及类型或接口外,方法的参数也应该考虑基类型或接口。以Enumerable类型为例,它的成员方法中只要涉及需要操作集合对象的地方,都要使用IEnumerable泛型接口,比如:public static IEnumerable Take(this IEnumerable source, int count){转载 2016-09-09 09:59:47 · 282 阅读 · 0 评论 -
建议98:用params减少重复参数
建议98:用params减少重复参数如果方法的参数数目不定,且参数类型一致,则可以使用params关键字减少重复参数声明。 void Method1(string str, object a){} void Method2(string str, object a,object b) { } void Method3(string st转载 2016-09-09 14:09:12 · 406 阅读 · 0 评论 -
建议99:重写时不应使用子类参数
建议99:重写时不应使用子类参数重写时,如果使用了子类参数,可能会偏离设计者的预期目标。比如,存在一个如下继承体系: class Employee { } class Manager : Employee { }现在,类型ManagerSalary中的SetSalary方法重写了Salary中的相同方法,重写的方法转载 2016-09-09 14:09:36 · 727 阅读 · 0 评论 -
建议100:静态方法和实例方法没有区别
建议100:静态方法和实例方法没有区别静态方法在加载时机和内存使用上和实例方法完全一致。在这里,我们先引出一个概念“类型对象”。比如类型Person,我们都知道new Person() 会产生一个对象,这个对象叫做“实例对象”,它在运行时会加载到GC Heap上。而“类型对象”是指代表Person类型本身的那个对象,这个对象在第一次使用类型时被加载到Loader Heap上。类型对象包括其转载 2016-09-09 14:09:45 · 660 阅读 · 0 评论 -
建议101:使用扩展方法,向现有类型“添加”方法
建议101:使用扩展方法,向现有类型“添加”方法考虑如何让一个sealed类型具备新的行为。以往我们会创建一个包装器类,然后为其添加方法,而这看上去一点儿也不优雅。我们也许会考虑修改设计,直接修改sealed类型,然后为其发布一个新的版本,但这依赖于你拥有全部的源码。更多的时候,我们会采取针对第三方公司提供的API进行编程的方式。对于我们来说,FCL是一组第三方公司(微软)提供给我们的最好转载 2016-09-09 14:09:56 · 379 阅读 · 0 评论 -
建议102:区分接口和抽象类的应用场合
建议102:区分接口和抽象类的应用场合 接口和抽象类有一些显而易见的区别:接口支持多继承,抽象类则不能。接口可以包含方法、属性、索引器、事件的签名,但不能有实现,抽象类则可以。接口在增加新方法后,所有的继承者都必须重构,否则编译不通过,而抽象类则不需要。这些区别导致两者的应用场景不同:如果对象存在多个功能相近且关系紧密的版本,则使用抽象类。如果关系不紧密,但若干功能转载 2016-09-09 14:10:11 · 489 阅读 · 0 评论 -
建议103:区分组合和继承的应用场合
建议103:区分组合和继承的应用场合 继承所带来的多态性虽然是面向对象的一个重要特性,但这种特性不能在所有的场合中滥用。继承应该被当做设计架构的有用补充,而不是全部。组合不能用于多态,但组合使用的频率却要远远高于继承。继承UML图如下:对应的代码如下: abstract class Stream { //省略转载 2016-09-09 14:10:19 · 800 阅读 · 0 评论 -
建议104:用多态代替条件语句
建议104:用多态代替条件语句假设要开发一个自动驾驶系统。在设计之初,此自动驾驶系统拥有一个驾驶系统命令的枚举类型: enum DriveCommand { Start, Stop }当前该枚举存在两个命令:开始、停止。又假设有一个驾驶方法可以处理车辆接收到的指令。一开始我们可能像下面这样编码:转载 2016-09-09 14:10:47 · 832 阅读 · 0 评论 -
建议105:使用私有构造函数强化单例
建议105:使用私有构造函数强化单例 单例指一个类型只生成一个实例对象。单例的一个简单实现如下所示: static void Main(string[] args) { Singleton.Instance.SampleMethod(); } public sealed class Singlet转载 2016-09-09 14:10:57 · 330 阅读 · 0 评论 -
建议106:为静态类添加静态构造函数
建议106:为静态类添加静态构造函数静态类可以拥有构造方法,这就是静态构造方法。静态构造方法与实例构造方法比较有几个自己的特点:只被执行一次,且在第一次调用类成员之前被运行时执行。代码无法调用它,不像实例构造方法使用new关键字就可以被执行。没有访问标识符。不能带任何参数。 使用静态构造方法的好处是,可以初始化静态成员并捕获在这过程中发生的异常。而使用静态成员初始化器则不能转载 2016-09-09 14:11:12 · 418 阅读 · 0 评论 -
建议107:区分静态类和单例
建议107:区分静态类和单例有一种观点认为:静态类可以作为单件模式的一种实现方式。事实上,这是不妥当的。按照传统的观点来看,单例是一个实例对象。而静态类并不满足这一点。静态类也直接违反面向对象三大特性的两项:继承和多态。无法让一个静态类从其它类型继承的实例如下: interface ISample { } static转载 2016-09-09 14:11:23 · 244 阅读 · 0 评论 -
建议108:将类型标识为sealed
建议108:将类型标识为sealedsealed能够阻止类型被其他类型继承。代码如下: sealed class SampleClass { } class OtherClass : SampleClass { }这段代码提示:“无法从密封类型SampleClass派生。转载 2016-09-09 14:11:32 · 279 阅读 · 0 评论 -
建议109:谨慎使用嵌套类
建议109:谨慎使用嵌套类使用嵌套类的原则是:当某类型需要访问另一个类型的私有成员时,才将它实现为嵌套类。一个典型的例子是在实现集合时,要为集合实现迭代器,这时用到了嵌套类。代码如下所示:public class ArrayList : IList, ICollection, IEnumerable, ICloneable{ //省略 public virtu转载 2016-09-09 14:11:46 · 419 阅读 · 0 评论 -
建议110:用类来代替enum
建议110:用类来代替enum枚举(enum)用来表示一组固定的值。例如,为了表示星期信息,我们可以定义枚举Week: enum Week { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday,转载 2016-09-09 14:11:58 · 871 阅读 · 0 评论 -
建议111:避免双向耦合
建议111:避免双向耦合双向耦合是指两个类型之间相互引用。下面的代码是一种典型的双向耦合: class A { private B b; public void MethodA() { b.MethodB(); } } class B {转载 2016-09-09 14:12:08 · 1095 阅读 · 0 评论 -
建议112:将现实世界中的对象抽象为类,将可复用对象圈起来就是命名空间
建议112:将现实世界中的对象抽象为类,将可复用对象圈起来就是命名空间在我们身边的世界中,对象是什么?对象就是事物,俗称“东西”。那么,什么东西算得上是一个对象呢?对象有属性、有行为。以动物为例,比如猫(Cat)。Cat可以有Name,这就是属性;Cat有一个恶习ScratchSofa(挠沙发),这就是行为。我们把这些属性和行为结合起来,就称为一个类型: class Ca转载 2016-09-09 14:12:22 · 850 阅读 · 0 评论 -
建议113:声明变量前考虑最大值
建议113:声明变量前考虑最大值假设正在开发一个工资系统,其中一个模块负责处理加薪。代码如下: static void Main(string[] args) { ushort salary = 65534; salary = (ushort)(salary + 1); Con转载 2016-09-09 14:12:38 · 373 阅读 · 0 评论 -
建议114:MD5不再安全
建议114:MD5不再安全MD5不再安全不是就算法本身而言的。如果从可逆性的角度出发,MD5值不存在被破解的可能性。MD5被广泛应用于密码验证和消息完整性验证。假设新注册一个用户,当注册用户的密码第一次被存储到数据库时,往往会将其转换为MD5值存储: static string GetMd5Hash(string input) {转载 2016-09-09 14:12:49 · 1454 阅读 · 1 评论 -
建议18:foreach不能代替for
建议18:foreach不能代替for上一个建议中提到了foreach的两个优点:语法更简单,默认调用Dispose方法,所有我们强烈建议在实际的代码编写中更多的使用foreach。但是,该建议也有不适合的场景。foreach存在一个问题:它不支持循环时对集合进行增删操作。比如,运行下面代码会抛出异常InvalidOperationException:转载 2016-08-31 13:28:03 · 1784 阅读 · 0 评论 -
建议19:使用更有效的对象和集合初始化
建议19:使用更有效的对象和集合初始化 依赖于属性和FCL 3.5之后的语法规则,现在我们有了更加简洁有效的对象和集合初始化机制:对象和集合初始化设定项。对象初始化: class Person { public string Name { get; set; } public int Age { get; set; }转载 2016-08-31 13:33:53 · 711 阅读 · 0 评论 -
建议20:使用泛型集合代替非泛型集合
建议20:使用泛型集合代替非泛型集合在建议1中我们知道,如果要让代码高效运行,应该尽量避免装箱和拆箱,以及尽量减少转型。很遗憾,在微软提供给我们的第一代集合类型中没有做到这一点,下面我们看ArrayList这个类的使用情况: ArrayList al=new ArrayList(); al.Add(0); a转载 2016-08-31 13:45:34 · 1466 阅读 · 0 评论 -
建议115:通过HASH来验证文件是否被篡改
建议115:通过HASH来验证文件是否被篡改 MD5算法作为一种最通用的HASH算法,也被广泛用于文件完整性的验证上。文件通过MD5-HASH算法求值,总能得到一个固定长度的MD5值。虽说MD5是一种压缩算法,以致可能存在多个样本空间会得到相同目标字符串的情况,但是这种概率很小。一个1GB的文件,哪怕只改动1字节的内容,得到的MD5值也会完全不同。示例代码:转载 2016-09-12 10:56:05 · 2631 阅读 · 0 评论 -
建议116:避免用非对称算法加密文件
建议116:避免用非对称算法加密文件MD5值或者说HASH值是一种不可逆的算法。如果需要从密文还原成明文,那么就需要对称和非对称这两类可逆算法了。对称算法示意图:在对称算法中,首先需要发送方和接收方协定一个密钥K。K可以是一个密钥对,但必须是加密密钥和解密密钥之间能相互推算出来的。在最简单也是最常用的对称算法中,加密和解密共享一个密钥。在上图,为了简单起见,使用的就转载 2016-09-12 10:56:19 · 2287 阅读 · 0 评论 -
建议117:使用SSL确保通信中的数据安全
建议117:使用SSL确保通信中的数据安全SSL(Secure Socket Layer)最初是由NetScape公司设计的,用于Web安全的网络协议。目前它已经广泛应用到各类网络传输通信中了。SSL利用数字证书技术(非对称加密),保证了通信过程中的唯一性、不可篡改性、不可抵赖性。SSL通道原理图:非对称加密中:秘钥分为两部分:公钥PK和私钥SK。公钥用于加密数据用转载 2016-09-12 10:56:29 · 871 阅读 · 0 评论 -
建议118:使用SecureString保存密钥等机密字符串
建议118:使用SecureString保存密钥等机密字符串托管代码中的字符串是一类特殊的对象,它们不可用被改变。每次使用System.String类张的方法之一时,或者使用此类型进行运算时(如赋值、拼接等),都要在内存中创建新的字符串对象,也就是为该新对象分配新的空间。这就带来了两个问题:原来的字符串是不是还在内存当中?如果在内存当中,那么机密数据(如密码)该如何保存才足够安全?转载 2016-09-12 10:56:49 · 4827 阅读 · 0 评论 -
建议119:不要使用自己的加密算法
建议119:不要使用自己的加密算法很多人认为自己写的加密算法才是安全的,因为该算法只有“自己知道”。很遗憾,这是大错特错。首先,我们不是秘密学专家,如果我们随随便便写个算法就称得上是加密算法的话,那么世界上就不会存在“密码学”这个专门的学科了。其次,应当记住的是:让数据安全的不是加密算法本身,而是密钥。当今世界上有许多流行的加密算法都是公开源码和逻辑的,如DES、A转载 2016-09-12 10:56:59 · 2821 阅读 · 0 评论 -
建议120:为程序集指定强名称
建议120:为程序集指定强名称虽然强名称在设计之初有防止被未授权的第三方软件非法执行程序的作用,但是因为它的破解方法并不难,所以现在强名称更多的意义在于它可以避免出现“DLL HELL”现象。 “DLL HELL”是指多个应用程序可能调用同一个DLL的情况。在应用程序使用过程中,常常会碰到这样一种情况:应用程序需要更新。在更新过程中,很有可能将会和别的应用程序公用的DLL也更新了。转载 2016-09-12 10:57:06 · 400 阅读 · 0 评论 -
建议121:为应用程序设定运行权限
建议121:为应用程序设定运行权限在某些情况下,可能存在这样的需求:只有系统管理员才能访问某应用程序的若干功能。这个时候,可以结合.NET中提供的代码访问安全性(Code Access Security)和基于角色(Role-Based Security)的安全性去实现。如果要通过一下的代码正常的访问类型SampleClass,用户必须以Administrator的身份运行代码:转载 2016-09-12 10:57:13 · 553 阅读 · 0 评论 -
建议122:以<Company>.<Component>为命名空间命名
建议122:以.为命名空间命名建议以.为程序集命名,比如Microsoft.Windows.Design。这有助于唯一地标识我们的命名空间。另外一种有效且肯定是唯一的表示命名空间的方式是使用域名。假设我们的域名是www.microsoft.com,那么命名空间应该命名为Com.Microsoft.。使用域名命名自己的程序的方法在Java世界中一直很流行,现在不妨把这种习惯带到.NE转载 2016-09-12 10:57:22 · 462 阅读 · 0 评论 -
建议123:程序集不必与命名空间同名
建议123:程序集不必与命名空间同名程序集一般会和命名空间同名,但这并不是必须的。事实上,不同名的命名空间和程序集是很常见的。程序集表示的是一种物理上的分组,而命名空间是逻辑上的分组,两者没有必然联系。当然,如果项目最终会被编译为dll,则我们更建议程序集和命名空间命名保持一致,这看上去更符合习惯。比如System.Data命名空间,对应的应该有一个System.Data.转载 2016-09-12 10:57:31 · 428 阅读 · 0 评论 -
建议124:考虑在命名空间中使用复数
建议124:考虑在命名空间中使用复数如果有一组功能相近的类型被分到了同一个命名空间下,可以考虑为命名空间使用复数。最典型的例子有,在FCL中,我们需要把所有的非泛型集合类集中在一起存放,所以就有了System.Collections命名空间。这样的命名规范,好处是即便没有使用过集合类的人,看到这个命名空间,也会知道它之下是和集合(即Collection)相关的一些类型。不要出现类似转载 2016-09-12 10:57:37 · 532 阅读 · 0 评论 -
建议125:避免用FCL的类型名称命名自己的类型
建议125:避免用FCL的类型名称命名自己的类型试想过自己写一个Socket类型吗?如果没有,我们来尝试一下:public class Socket{ //省略 }把以上代码同某些其他工具类封装到某个dll里,让其他人调用。调用者代码如下:public class SampleInvoker{ public void DoSomethin转载 2016-09-12 10:57:48 · 385 阅读 · 0 评论 -
建议126:用名词和名词组给类型命名
建议126:用名词和名词组给类型命名类型对应着现实世界中的实际对象。对象在语言中意味着它是一个名词。所以,类型也应该以名词或名词词组去命名。类型定义了属性和行为。虽然它包含行为,但不是行为本身。所以,下面的一些命名对于类型来说是好的命名:OrderProcessorScoreManagerCourseRepositoryUserControl转载 2016-09-12 10:57:56 · 353 阅读 · 0 评论 -
建议127:用形容词组给接口命名
建议127:用形容词组给接口命名接口规范的是“Can do”,也就是说,它规范的是类型可以具有哪些行为。所以,接口的命名应该是一个形容词,如:IDisposable表示可以被释放IEnumerable表示类型含有Items,可以被迭代。正是因为接口表示的是类型的行为,所以从语义上可以让类型继承多个接口,如: class SampleClass :转载 2016-09-12 10:58:07 · 1487 阅读 · 0 评论 -
建议128:考虑让派生类的名字以基类名字作为后缀
建议128:考虑让派生类的名字以基类名字作为后缀派生类的名字可以考虑以基类名字作为后缀。这带来的好处是,从类型的名字上我们就知道它包含在哪一个继承体系中。Exception及其子类就是这样一个典型的例子。所有的异常都应该继承自System.Exception,而所有的异常都应该命名为CustomedException。如果在VS中输入Exception,再按Tab键,会自动生成如下转载 2016-09-12 10:58:14 · 705 阅读 · 0 评论 -
建议129:泛型类型参数要以T作为前缀
建议129:泛型类型参数要以T作为前缀作为一种约定,泛型类型的参数要以T作为前缀。如委托声明:Action其中,泛型类型参数名不应该处理成:Action当然,这仅仅是一种习惯,若果使用第二种命名方式,编译器并不会报错,但是作为调用者,也许不能意识到这里是一个泛型类型参数。这个问题在为类型指定泛型的时候尤为明显,因为为类型指定泛型类型参数的声明不会出现在公开的转载 2016-09-12 10:58:24 · 1079 阅读 · 0 评论 -
建议130:以复数命名枚举类型,以单数命名枚举元素
建议130:以复数命名枚举类型,以单数命名枚举元素枚举类型应该具有负数形式,它表达的是将一组相关元素组合起来的语义。比如: enum Week { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday,转载 2016-09-12 10:58:37 · 958 阅读 · 0 评论 -
建议131:用PascalCasing命名公开元素
建议131:用PascalCasing命名公开元素开放给调用者的属性、字段和方法都应该采用PascalCasing命名方法,比如: class Person { public string FirstName; public string LastName; public string Name {转载 2016-09-12 10:58:50 · 549 阅读 · 0 评论