《Effective c#》读书笔记之笔记
2012-10-25 11:11 露珠的微笑 阅读(180) 评论(0) 收藏 举报摘抄于晴天猪的博客文章,以备以后回顾理解
1、C#两种类型常量
C#中有两种类型的常量:编译期常量readonly和运行时常量const(后者运行较快,不如前者灵活)。应尽量使用运行时常量,应仅仅在那些性能异常敏感,且常量的值在各个版本之间绝对不会变化时,才使用编译常量 ,二者的区别
编译常量的值是在目标代码中进行替换的,运行时常量将在运行时求值,在构造函数执行后不能被再次修改
因此if(myDateTime.Year == Millennium)和if(myDateTime.Year == 2000)等同
编译期常量仅能用于基本类型(内建的整数和浮点类型)、枚举或字符串。在编译后得到的IL代码中,只有这些常量可以直接被替换成为它们的字面值。
例子:
假设:在一个名为Customer的程序集中分别定义了一个const字段和readonly字段:
1 class Customer 2 { 3 public static readonly int StartValue = 5; 4 public const int EndValue = 10; 5 }
在另一个程序集中引用了这两个值,当我们过了一段时间需要更新Customer程序集的这两个字段将值更改:
1 class Customer 2 { 3 public static readonly int StartValue = 15; 4 public const int EndValue = 20; 5 }
随后,分发Customer程序集,而没有重新编译整个应用程序时,我们可以发现在并没有重写编译整个应用程序的情况下所以引用了readonly字段的值变成了我们更新的值,而其他引用了const字段的值却没有更新。
总结:只有在编译期必须获得确定数值时一定要使用const。例如特性(attribute)的参数和枚举的定义等,还有那些在各个版本发布之间不会变化的值。在除此之外的所以情况下,都应该经理选择更加灵活的readonly常量
2、强制转换:is或as而不是强制转换
C#为类型转换提供两种选择:①使用强制类型转换②使用as操作符
首先用is测试一下,能否转换成功,然后再使用as或者强制类型转换。在大部分情况下,我们应该尽可能的使用as操作符:因为相对于强制类型转换来说,as更安全,也更加高效。as和is操作符都不会执行任何用户自定义的转换,它们仅当运行时类型符合目标类型时才能转换成功,也不会在转换时创建新的对象。
注意:as操作符的限制——as操作符不能配合值类型使用,因为它们永远都不可能为null
is操作符的使用——在不确定的强制类型转换中使用异常来控制程序流是一个很糟糕的做法,在转换之前我们可以使用is来避免冒出异常或转换。
当不能使用as进行转换时,才应该使用is操作符。否则is是多余的;因为使用as操作符只需要检查是否为null即可,这样更加简单
小结:好的面向对象实践一般都告诉我们要避免转型,但有时侯你却别无选择。在无法避免转型时,我们应该尽可能的使用as和is操作符来更清晰的表达意图。不同的转型方式有不同的规则,is和as操作符在绝大多数情况下都能表达出正确的语义,只有当被测试的对象时正确的类型时才会成功。应尽量选择is和as而不是强制类型转换,因为强制类型转换可能会带来意想不到的负面效应,而且成功或失败往往在我们的预料之外。
3、为类型提供ToString()方法(可参考http://www.cnblogs.com/go-go-go/archive/2012/10/25/2738741.html)
我们创建的每一个类型都应该能覆写Object类的ToString()方法。如果我们想要拥有更加详细的返回,可以通过覆写IFormattable接口来解决,该接口包含了一个重载的ToString()方法,它允许我们为类型提供不同形式信息。
注意在C#3.0中,编译器会为所以匿名类型创建一个默认的ToString()方法。该默认方法将显示对象中的每个属性值。
例子
public class Customer : IFormattable { //属性字段省略... public string ToString(string format, IFormatProvider formatProvider) { if (formatProvider != null) { ICustomFormatter fmt = formatProvider.GetFormat(this.GetType()) as ICustomFormatter; if (fmt != null) return fmt.Format(format, this, formatProvider); } switch (format) { case "r": return Revenue.ToString(); case "p": return ContactPhone; case "nr": return string.Format("{0,20},{1,10:C}", Name, Revenue); case "np": return string.Format("{0,15},{1,10:C}", Name, ContactPhone); case "n": case "G": default: return Name; } } }
Customer使用者即可自定义其想要输出的格式:
1 IFormattable c1 = new Customer(); 2 Console.WriteLine("Customer record:{0}", c1.ToString("nr", null));
首先我们必须支持表示通用格式的"G";其次我们必须支持两种格式的空格式,即" "和null。这三种格式返回字符串都必须和Ojbect.ToString()的覆写版本的字符串相同。实现了IFormattable接口的类型,.NET BCL都会调用IFormattable()而不是Object.ToString()。
4、理解装箱和拆箱
装箱:是值类型——>引用类型或是该值类型实现的接口类型,在堆中分配一个对象实例,并将该值复制到新的对象中。
拆箱:将从对象中提取值或接口类型到实现该接口的值类型(首先检查对象实例,以确保它是给定值类型的装箱值;然后将该值从实例复制到值类型变量中)
5、C#等同性判断
在C#中为我们提供了四种不同的函数来判断两个对象是否”相等“:
1 public static bool ReferenceEquals(object left, object right); 2 public static bool Equals(object left, object right); 3 public virtual bool Equals(object right); 4 public static bool operator ==(MyClass left, MyClass right);
对于前两个静态函数,我们永远都不应该去重新定义,因为它们已经很好的完成了它们的工作,且判断与运行时具体类型无关:判断两个不同变量的对象标志(object identity)是否相等。无论比较的是值类型还是引用类型静态的ReferenceEquals方法的判断依据都是对象标志,所以比较两个值类型永远返回false,即使是值类型和它本身比较也是,这是因为装箱的原因
①值类型(使用Struct关键字创建的类型):System.ValueType(所有值类型的基类)覆写了Object.Equals()方法:两个值类型变量类型相同,内容一致,两个变量才认为相等。由于ValueType是所有值类型的基类,为了提供正确的行为,必须能够在不知道对象运行时类型的情况下比较其派生类中的所有成员变量,这意味着要使用反射来实现。而反射又是非常损耗性能的。而等同性判断又是一个非常基础的功能,所以我们有必要(追求性能时)为自己的值类型提供一个更快的Equals()覆写版本。
②引用类型:只有我们希望更改其预定义的语义时,才应该覆写Equals()方法。在.NET类库中许多类都是使用值语义而不是引用语义来做等同判断的,例如:如果两个string对象包含相同的内容就被认为相等;若两个DataRowView对象引用同一个DataRow,那么将被认为相等。
③ 覆写Equlas():需要实现IEquatable<T>接口
待续....
浙公网安备 33010602011771号