
Effective C#
尹自强
这个作者很懒,什么都没留下…
展开
-
Effective C# Item10:理解GetHashCode()方法的缺陷
GetHashCode()方法的用途:为一个基于散列的集合定义键的散列值,典型的散列集合包括HashTable和Dictionary。如果我们定义的类型在散列集中不会被用作键的话,那么不用关心它的GetHashCode()方法是否高效和正确。 作为类型的散列值,它应该满足以下三条规则: 如果两个对象相等,那么它们必须产生相同的散列值,否则,这样的三列码不能用来查找集合中的对象。对于转载 2014-07-24 11:01:42 · 403 阅读 · 0 评论 -
Effective C# Item29:只有当新版基类导致问题时才考虑使用new修饰符
我们一般在类成员上使用new修饰符,来重新定义继承自基类的非虚成员,但是我们不建议这么做,因为重新定义非虚方法会导致含混不清的行为。 非虚方法是静态绑定的,编译器并不会根据对象的运行时类型来判断应该调用哪个方法;而虚函数使用的是动态绑定,编译器会根据对象的运行时类型来判断应该调用哪个方法。 避免使用new 修饰符来重新定义非虚方法,并不是说你应该将基类中的所有方法都设计成虚方法转载 2014-07-25 14:51:07 · 368 阅读 · 0 评论 -
Effective C# Item15:利用using和try/finally语句来清理资源
在内存资源分配方面,.NET可以分为托管资源和非托管资源两部分。对于托管资源,.NET框架会负责进行垃圾回收的工作;对于非托管的资源,我们需要手动去释放资源。通常情况下,我们使用IDisposable接口中的Dispose()方法释放资源,而调用Dispose()方法的责任,由资源的拥有者或者对象的拥有者执行,即我们这些程序员,确保调用Dispose()方法的最佳方式就是使用using或者try/转载 2014-07-25 08:58:10 · 472 阅读 · 0 评论 -
Effective C# Item24:声明式编程优于命令式编程
声明式编程是一种更简单、更精练的描述软件程序行为的方式,它意味着可以使用声明、而非指令的方式来定义程序的行为。C#中大部分编程都是命令式编程,即通过编写方法来定义程序的行为。我们可以通过使用特性,来在C#中实现声明式编程,它更易于实现、阅读和维护。 .NET框架本身已经为我们提供了大量的特性,我们可以利用这些特性非常方便的实现声明式编程;当.NET框架提供的特性不能满足我们的需求时,我们转载 2014-07-25 14:06:41 · 647 阅读 · 0 评论 -
Effective C# Item25:尽可能将类型实现为可序列化的类型
持久化是类型的一个核心特性,有时我们需要通过不同的方式传输和创建同一个对象,例如需要通过网络传输对象,或者需要将对象信息存储到文本文件或者XML文件中,这时,如何能够保持对象的状态,并在将来使用时,可以准确的还原到原来的状态,是非常重要的。 .NET可以使用序列化的方式来持久化对象,我们在可能的情况下,应该将类型定义为可以序列化的。序列化时,我们可以使用Serializable特性。转载 2014-07-25 14:21:37 · 318 阅读 · 0 评论 -
Effective C# Item18:实现标准Dispose模式
如果一个类型中包含了非托管的资源,那么我们应该自己编写释放非托管资源的方法。.NET提供了一个标准的用于释放资源的模式,叫做Dispose模式,在这种模式中,类型实现IDisposable接口,并提供一个终结器。这样,正常流程下类型的使用者调用Dispose()方法来释放资源,如果用户忘记调用Dispose()方法, 那么类型的终结器会作为最后的保障来释放对象的非托管资源。 在Dispo转载 2014-07-25 09:05:54 · 461 阅读 · 0 评论 -
Effective C# Item19:定义并实现接口优于继承类型
这个话题不仅仅是针对.NET的,在其他面向对象语言的环境中,例如Java,都会有接口和抽象类,对于究竟是选择接口还是抽象类,已经有了太多的讨论,包括设计模式中都有了一条设计原则:组合优于继承,虽然这条原则和我们要讨论话题没有太大联系,但是可以看出在这方面如何做出选择,并没有一个万能的解决方案,一般都要见招拆招,具体问题具体分析。 抽象类一般是作为一个类层次结构的顶端,它一般有两个用途:1)转载 2014-07-25 09:07:44 · 521 阅读 · 0 评论 -
Effective C# Item27:避免ICloneable接口
一般情况下,我们不建议针对类型实现ICloneable接口。因为如果一个类型支持ICloneable接口,那么该类型的所有派生类都必须实现它,而且类型中所有成员类型也都要实现ICloneable接口,或者有其他创建复制对象的机制,当我们设计的类型包含交织成网状的对象时,支持深复制会变得比较复杂。 复制操作分为浅复制和深复制两种,其中浅复制是指新对象包含所有成员变量的副本,如果成员变量为引转载 2014-07-25 14:31:59 · 435 阅读 · 0 评论 -
Effective C# Item20:明辨接口实现和虚方法重写
接口实现和虚方法重写看上去很像,它们都可以对定义在另一个类型中的方法进行重新实现,但是,这两种方式有很大的区别。 我们来看下面的代码,首先定义一个接口。 public interface InterfaceA { void MethodA(); } 我们可以采取以下三种方式来实现上述接口InterfaceA。转载 2014-07-25 09:08:23 · 505 阅读 · 0 评论 -
Effective C# Item22:使用事件定义外发接口
事件为类型定义了外发接口,C#的事件是建立在委托的基础上的,委托为事件处理器提供了类型安全的函数签名。 委托要比事件的使用范围广泛,我们可以把事件看做是一种经过了封装的委托,专门用于事件驱动模型。你可以在客户代码中直接调用委托来激发委托指向的函数,而事件不可以,你只能在服务端调用事件,在客户端调用事件是会引发编译错误的。我们来看下面的程序。 public class Event转载 2014-07-25 09:17:06 · 443 阅读 · 0 评论 -
Effective C# Item28:避免强制类型转换
转换操作为类之间引入了一层“可替换性”,“替换”意味着一个类的实例可以被替换为另一个类的实例。例如,我们在一个类层次结构中,在任何使用父类的地方,我们可以使用子类的实例进行替代,这是“多态”的作用。 当我们为类型定义了类型转换操作符后,我们实际上是在告诉编译器这些类型可以被当做目标类型来使用,这样的替换经常会导致一些很诡异的Bug,因为我们的类型可能并不是目标类型的完美替代品。转载 2014-07-25 14:39:55 · 445 阅读 · 0 评论 -
Effective C# Item16:尽量减少内存垃圾
虽然.NET提供了垃圾回收机制,可以对托管的资源进行管理,但是创建对象和销毁对象本身,也是要花费时间的,特别是对于引用类型来说,频繁的创建和销毁引用类型的对象,对性能来说,是非常不好的。 我们可以采取一些措施,来改善这种情况。 首先,来看下面的代码。 protected override void OnPaint( PaintEventArgs e ) { // Bad.转载 2014-07-25 08:59:40 · 398 阅读 · 0 评论 -
Effective C# Item17:尽量减少装箱和拆箱
装箱和拆箱存在的意义:值类型是数据的容器,它存储在堆栈上,不具备多态性,而.NET框架在整个对象层次的设计中,使用System.Object作为所有类型的基类,但是Obejct是引用类型,而作为值类型的基类System.ValueType,是从System.Object派生出来的,这就产生了矛盾,装箱和拆箱就是为了解决这两种类型之间的差异。 装箱会将一个值类型放入一个未具名类型(unty转载 2014-07-25 09:03:31 · 447 阅读 · 0 评论 -
Effective C# Item31:尽可能实现短小简洁的方法
我们推荐编写短小简洁的方法,而不是冗长复杂的方法,主要的原因在于.NET的JIT机制。 当使用C#代码编写的程序在运行时,需要经历两个步骤:1)C#编译器将我们编写的源代码编译为IL中间代码;2)CLR通过调用JIT编译器将IL代码转换成本地机器代码。这两个步骤会分摊在整个应用程序运行的过程中。CLR并不会再应用程序启动时对整个应用程序做JIT编译,而是以方法为单位进行JIT编译。这可以转载 2014-08-05 10:08:03 · 499 阅读 · 0 评论 -
Effective C# Item30:尽可能实现CLS兼容的程序集
CLS是一套针对编程语言的变成规范,.NET环境对语言没有特定的限定,只要是符合CLS规范的语言,我们就可以说它是和.NET兼容的语言。在实际的项目开发过程中,我们可以使用不同的语言,例如,当我们需要引入一些第三方产品的程序集时,我们不能保证程序集中使用的语言是和我们使用的编程语言是一致的,因此,确保程序集必须是CLS兼容的。 CLS兼容性实际上采用一种“最大公分母”的方式来实现互操作的转载 2014-08-05 10:06:13 · 524 阅读 · 0 评论 -
Effective C# Item21:使用委托表达回调
回调用于为服务器和客户端之间提供异步的反馈,其中可能会涉及到多线程或者需要提供一个入口点用于同步更新,在C#中,我们使用委托来表达回调。 委托为我们提供了类型安全的回调定义,虽然大多数常见的委托应用都和事件相关,但是那并不是委托应用的全部场合。当类之间有通信的需要,并且我们期望一种比接口更加松耦合的机制时,委托就是最合适的选择。委托允许我们在运行时配置目标,并且可以通知多个对象,委托对象转载 2014-07-25 09:15:14 · 462 阅读 · 0 评论 -
Effective C# :创建二进制组件
二进制组件允许我们将各个功能分解后单独发布,.NET中将这种类型的二进制组件称作程序集,在共享逻辑、跨语言以及组件部署方面,程序集为我们提供了很多简化和便利。 程序集在.NET中充当了组件包的角色,每个程序集都可以独立的发布和升级,制约我们升级一个程序集的因素是“如何尽量减少程序集之间的耦合”。 当程序启动时,CLR加载器并不会加载所有被引用的程序集,而是之用当CLR需要某个程转载 2014-07-25 15:01:10 · 450 阅读 · 0 评论 -
Effective C# Item26:使用IComarable和IComparer接口实现排序关系
.NET框架定义了两个接口来描述类型的排序关系:IComparable和IComparer,其中IComparable接口定义了类型的自然排序方式,IComparer则为类型提供了另外的排序方式。 我们来看下面的代码。 public struct Employee : IComparable { private string m_strName;转载 2014-07-25 14:24:43 · 560 阅读 · 0 评论 -
Effective C# Item9:理解几个相等判断之间的关系
C#中,有四种方式可以应用于“相等判断”,如下。 public static bool ReferenceEquals( object left, object right ); public static bool Equals( object left, object right ); public virtual bool Equals( object right);转载 2014-07-24 10:59:47 · 399 阅读 · 0 评论 -
Effective C# Item8:确保0是值类型的有效状态
.NET会将对象的值默认设置为0,对于值类型来说,例如struct,我们无法阻止开发人员将结构体中的成员设置为0,因此,我们需要将0设置为有效状态。 对于枚举类型来说,如果通过new的方式新建一个枚举类型的对象,那么对象的值是0,代码如下。 代码 public enum EnumSex { Female = 1, Male = 2转载 2014-07-24 10:57:06 · 452 阅读 · 0 评论 -
Effective C# Item4:使用Conditional特性代替#if条件编译
条件编译的目的:希望同样的代码在不同的环境中生成不同的结果,最典型的情况就是在Debug模式和Release模式下,能够产生不同的编译结果。 C#中,针对上述情况,有两种解决方案:1. 旧有的#if、#endif方式;2. 使用Conditional特性。 #if方式存在的问题: 如果在代码中以“流水账”的方式添加#if,很容易造成代码可读性差,在发生错误后,很难进行调试转载 2014-07-24 10:42:12 · 467 阅读 · 0 评论 -
Effective C# Item2:运行时常量(readonly)优于编译时常量(const)
C#中的常量分为两种类型:运行时常量和编译时常量,运行时常量使用readonly修饰,编译时常量使用const修饰。 运行时常量和编译时常量有以下区别: 运行时常量是在程序运行时才会进行解析,而编译时常量是在程序进行编译时,就进行解析了。从生成的IL来看,运行时常量在IL中依然会指向你声明的变量;而编译时常量在IL中已经变为具体的值了。编译时常量只能应用于基本类型,包括数字和字符串转载 2014-07-24 10:36:42 · 426 阅读 · 0 评论 -
Effective C# Item11:优先采用foreach循环语句
C#针对循环提供了一种新的形式:foreach,它和.NET框架中的集合接口密切联系。我们在程序中,应该优先使用foreach进行循环。 来看下面的代码片段转载 2014-07-24 11:05:14 · 415 阅读 · 0 评论 -
Effective C# Item12:变量初始化器优于赋值语句
之所以推荐在变量声明时进行初始化,是因为我们定义的类型往往有多个成员变量,这样会导致有多个构造器,这样成员变量和构造器之间很可能做不到同步更新。我们可以通过“在声明变量的同时初始化它们”的方式来解决这个问题。 但是,以下三种情况,是不适合在变量初始化时对其进行赋值的。 当我们要将对象初始化为0或者null时,这时在变量初始化时为其赋值是没有错的,但是效率不高。原因是系统默认的初始化转载 2014-07-24 11:07:07 · 400 阅读 · 0 评论 -
Effective C# Item7:将值类型尽可能实现为具有常量性和原子性的类型
在创建值类型实例时,应该尽可能将其实现为具有常量性和原子性,所谓常量性,就是说对象在创建后就不会被改变,类似于我们在程序中定义的常量,这种形式的值类型只要被创建好后,就可以认为它的状态一直是不变的,这样它就是线程安全的。 原子性是指值类型中某些具有关联关系的字段,例如生日和年龄,年龄是根据生日计算得来的,如果对象的使用方只是修改年龄,而不修改生日,那么这样的结果显然是错误的。具有原子性的转载 2014-07-24 10:54:23 · 404 阅读 · 0 评论 -
Effiective C# Item1 : 使用属性代替成员变量
Item1:Always Use Properties Instead of Accessible Data Members 属性是C#语言中一个很重要的特性,我们可以使用属性对成员进行封装。 从编译器的角度来看,属性就是针对成员变量两个方法:get_xxx和set_xxx,这一点和Java是很像的,当我们在Java中定义个成员变量,然后通过重构生成相应的属性时,对应的方法名就转载 2014-07-24 10:35:51 · 645 阅读 · 0 评论 -
Effective C# Item6:明辨值类型和引用类型的使用场合
可能是受到Java的影响,我在转向C#后,一直没有注意区分过值类型和引用类型,不论是编写新代码还是重构旧代码时,如果发现有一些信息需要独立出来时,一直都是以新建类的方式进行,很少考虑使用结构体(我到目前为止,对结构体的理解还停留在C语言的层次)。 C#,或者说.NET,是区分值类型和引用类型的,这一点和C++以及Java都有区别。C++中传参都是以“传值”的方式进行,这种方式效率很高,但转载 2014-07-24 10:53:30 · 468 阅读 · 0 评论 -
Effective C# Item5:总是提供ToString()方法
C#中,System.Object默认提供了ToString()方法, 它会返回当前对象的完整类型名称,即命名空间 + "." + 类名,这种信息,对于类的使用者或者终端用户来说,可读性都很低,因此,建议在新建一个类时,重写ToString()方法。 如果不重写ToString()方法,那么为了向用户显示一些有意义的信息,就需要在每个调用这个类的地方,编写相同的代码,这样会造成很大的代码转载 2014-07-24 10:49:08 · 371 阅读 · 0 评论 -
Effective C# Item3:操作符as或is优于强制转换
C#是一种强类型的变成语言,我们一般情况下,不推荐大家对变量的类型进行转换,但是针对代码底层来说,为了对业务代码提供尽可能多的支持,很多时候对方法参数的类型不会做强行限制,而只是将其置为System.Object,那么业务在调用这些方法时,如果希望在方法体内执行用户自定义的方法,就必须对参数的类型进行转换,将System.Object转换为用户自己定义的类型。 关于类型转换,C#有以下两转载 2014-07-24 10:40:56 · 419 阅读 · 0 评论 -
Effective C# Item13:使用静态构造器初始化静态类成员
在创建类型的任何实例之前,我们需要初始化这个类型中的静态成员变量,C#可以通过静态初始化器或者静态构造函数来完成这个任务,其中静态构造器是一个特殊的函数,它会在一个类的任何方法、变零或者属性被访问之前执行。 我们不应该使用类型的示例构造函数或者类型中的某些非静态方法来初始化类型的静态成员变量。 如果静态成员初始化的过程不复杂,那么我们可以在类型的初始化器中对其进行初始化;如果静转载 2014-07-24 11:11:05 · 436 阅读 · 0 评论 -
Effective C# Item14:利用构造器链
编写构造函数通常是一件重复性的劳动,我们经常需要在不同声明形式的构造函数中,对同一个成员变量进行同样的初始化操作,为了完成这个任务,通常有以下三种方式。 对代码采用“复制/粘贴”的方式,在写好一个构造函数后,将代码粘贴到其他构造函数中。 采用辅助函数的方式,将共通的逻辑放置到一个单独的方法中,然后在每个构造函数中调用这个方法。 采用构造器链的方式,在一个构造函数中,使用另外的构造转载 2014-07-24 11:12:33 · 431 阅读 · 0 评论 -
Effective C# Item23:避免返回内部类对象的引用
当我们将属性置为只读后,就可以认为调用者对该属性的值不可以进行更改了吗?答案是否定的。对于值类型来说,将其置为只读,确实可以让调用者不能够对其进行修改;但是对于引用类型来说,调用者还是可以调用引用对象的公有成员,包括那些可以修改属性状态的成员。 我们来看下面的代码。 public struct Employee { private string m_st转载 2014-07-25 14:02:26 · 476 阅读 · 0 评论 -
Effective C# Item32:尽可能实现小尺寸、高内聚的程序集
我们在划分程序集时,经常会犯的一个错误:在一个程序集中放入了太多的东西,导致程序集很难被重用。 一个好的程序集应该是“高内聚”的,所谓内聚,是指将程序集封装为一个有意义、有职责的单位,它一般可以用一句话简单的话来描述,例如.NET框架中的System.Collection程序集就为存储一组相关的对象提供了数据结构。我们不能这么说:MyApplication程序集提供了任何我们所需的功能,转载 2014-08-05 10:14:28 · 481 阅读 · 0 评论