保证0为值类型的有效状态<.NET资源管理>
.NET系统的默认初始化过程会将所有的对象设置为0。我们就会难免创建出一个初始化为0值的值类型,所以我们应该将0作为类型的默认值,可以避免一些不必要的Bug。
1.将0设置为枚举的有效值
使用枚举时我们必须将0设置为枚举的一个有效选项。所以枚举值都派生于System.ValueType。枚举默认的值开始于0,但是也可以自定义:
1 public enum Month 2 { 3 //枚举的默认值是从0开始 4 Jan = 1, 5 Feb = 2, 6 Mar = 3, 7 April = 4, 8 May = 5, 9 }
但是如果实例化一个Month,我们将会得到一个为0的默认值,而这并不是Month的合法值:
1 Month month = new Month(); 2 Console.WriteLine((int)month); 3 //输出0
因此在创建自定义的枚举的时候,必须确保0是一个合法的有效选项,如果可能应该将最适合做默认值的选项表示0,如果没有合适的选项适合做默认值的话,可以把0表示未初始化的值(逻辑上),让类型的使用者知道它当前的状态,例如修改成下面这样:
1 public enum Month 2 { 3 //枚举的默认值是从0开始 4 None = 0, 5 Jan = 1, 6 Feb = 2, 7 Mar = 3, 8 April = 4, 9 May = 5, 10 }
2.正确初始化包含引用的值类型
对于包含引用的值类型(如:字符串),也是比较在初始化的时候出现问题:
1 public struct LogMessage 2 { 3 private int ErrLevel; 4 private string msg; 5 }
在上面的程序中, 如果实例化一个LogMessage对象,msg字段为一个空引用,且不能直接对其进行初始化(private访问修饰符),不过我们可以通过使用属性类解决这个问题,通过属性将msg字段暴露给外界使用者。然后在属性中添加逻辑:在msg为null是返回一个空字符串:
1 public struct LogMessage 2 { 3 private int ErrLevel; 4 private string msg; 5 6 public string LogMessage 7 { 8 get 9 { 10 return (msg! = null)?msg:string.Empty; 11 } 12 set 13 { 14 msg = value; 15 } 16 } 17 }
这样做的好处就是将null引用的检查限制在一个单一的类型中,假如在程序集内部调用,那么Message属性很可能会被内联。这样既保证了代码的效率,也降低了错误发生的可能。
小节
系统的初始化行为让所有的值类型的值都设置为0,这是我们无法避免的,我们能做的就是尽量将0设置为最可能的默认值,反正,也应该将0作为一个有效的合法选项,就行我们前面的第一个示例一样。
标签: Effective C#
读书笔记
摘要: .NET系统的默认初始化过程会将所有的对象设置为0。我们就会难免创建出一个初始化为0值的值类型,所以我们应该将0作为类型的默认值,可以避免一些不必要的Bug...阅读全文
摘要: C#和JAVA不同,在C#中可以创建值类型,而在Java中创建的所有类型都是引用类型。在使用C#开发时,选择值类型还是引用类型对我们的程序的行为会产生很大的影响,所以我们需要对值类型和引用类型的使用场景进行了解和对二者的不同进行区分。C#之所以添加了值类型和引用...阅读全文
摘要: 如何为我们自己的包含非托管资源的类型编写资源管理代码呢?在 .NET 中为我们提供了一种标准的销毁非托管资源的模式,这个标准的模式能够使使用者通过调用IDisposable接口正常释放掉非托管资源,也能够保证使用者在忘记释放资源时使用终结器释放。阅读全文
摘要: 我们知道:C#是一门虚拟机语言,C#编译器首先将C#代码编译成IL代码,运行程序时CLR(Common Language Runtime,公共语言运行时)通过调用JIT(just-in-time Compiler,即时编译器)来将IL代动态即时编译成可执行的机器码。GC(Garbage Collector,垃圾收集器)自动为我们的应用程序进...阅读全文
摘要: 一个类通常会有多个构造函数,并且随着时间的推移,成员变量的增加,功能的改变,构造函数的个数也会不断上升。很多的开发人员一般会先编写一个构造函数,然后将其代码复制粘贴到其他的构造函数当中,以支持在类接口上定义的多个重写构造函数.其实我们不应该这样做,当发现...阅读全文
摘要: 我们知道在C#语言中创建一个类型的实例前,就应该初始化该类型的所有静态成员变量。C#语言为我们提供了静态初始化器和静态构造函数。其中,静态构造函数是一个特殊的构造函数,将在其他所有方法执行前以及变量或属性被第一次访问之前将自动调用静态构造函数,且仅执行一次...阅读全文
摘要: 一般情况下,一个类都会有多个构造函数。随着时间的推移,成员变量、构造函数不断增加。为了处理这种情况最方便的办法就是:在声明变量的时候进行初始化,而不是在每个构造函数中进行。无论是类成员(静态变量)合适实例变量,我们都应该充分利用初始化器的语法。C#编程在,一般在...阅读全文
摘要: 我们知道C#是一门虚拟机语言,在C#编译器将C#代码编译成IL代码后,运行在.NET CLR(公共语言运行时)中,运行程序是CLR通过调用JIT(即时编译器)来将IL代动态即时编译成可执行的机器码。在CLR中有一个非常重要的概念:CLR GC(Garbage Collector,垃圾收集器),GC自动为我们的应用程序进...阅读全文
摘要: 在应用程序开发过程中,开发者都力求写出更加高效的代码。但是当你想手工为C#编译器优化代码时,你的种种优化可能反倒会阻碍JIT进行更加高效的优化。因此,我们最好尽可能的写出最清晰的代码,将优化工作交给JIT编译器去完成。在.NET平台下开发程序的开发者都应该知道...阅读全文
摘要: C#4.0 引入了具名参数(MSDN翻译为“命名实参”,个人认为具名参数更形象,可选参数亦是)和可选参数(可选实参)。客户端代码使用具名参数意味着:方法中的参数名称也成为了公有接口的一部分。假如修改公有参数的名称将有可能破坏调用者的代码。这意味着:调用者应该尽可...阅读全文
摘要: 查询语法(query syntax)可以让程序逻辑的表达由“命令式”转换为“声明式”。查询语法定义了想要的结果,而把具体实现交给其他的专门实现。使用查询语法(实现了查询表达式模式的方法语法也可以)要比传统的命令式循环结果更加清晰的表达你的意图...阅读全文
摘要: C#允许我们创建两种类型:值类型和引用类型。如果两个引用类型的变量指向的是同一个对象,它们将被认为是“引用相等”。如果两个值类型的变量类型相同且包含同样的内容,它们被认为是“值相等”。这也正是同等性判断需要如此多方法的原因...阅读全文
摘要: ToString()方法作为.NET环境中最常用的方法之一,我们应该为类型的所有者提供一个合理的ToString()版本,类型的字符串表示可用来在不同的环境下向用户轻松显示对象的相关信息;此外,类型的字符串表示还可以拥有调试环境。因此,我们创建的每一个类型都应该能覆写...阅读全文
摘要: #if/#endif 语句常用来基于同一份源码生成不同的编译结果,其中最常见的就是debug版和release版。但是这些工具在实际应用中并不是非常友好,因为它们容易被滥用,其代码页进而难以理解或调试。C#设计中考虑到这个问题,并提供了更好的工具——Conditional特性...阅读全文
摘要: 因为相对于强制类型转换来说,as更安全,也更加高效。as和is操作符都不会执行任何用户自定义的转换,它们仅当运行时类型符合目标类型时才能转换成功,也不会在转换时创建新的对象...阅读全文
摘要: C#语言中有两种类型的常量:编译期常量和运行时常量。应该尽量使用运行时常量,而不是编译期常量。虽然编译期常量略快一些,但是没有运行时常量那么灵活。应仅仅在那些性能异常敏感,且常量的值在各个版本之间绝对不会变化时,才使用编译器常量...阅读全文
摘要: 属性允许将数据成员作为公共接口的一部分暴露出去,同时仍旧提供面向对象环境下所需要的封装。属性这个语言元素可以让你像访问数据成员一样使用,但底层依旧使用方法实现。通过使用属性我们可以创建出类似于数据访问(客户代码在访问属性时,就像是在访问共有的字段),但实际上确是方法调用的接口...阅读全文
摘要: 1.任何不会被修改的变量都可以作为参数传入新的函数,至于会被修改的变量就需要格外小心。(p9)2.重构技术就是以微小的步伐修改程序。如果你犯下错误,很容易便可以发现它。(p13)3.好的代码应该清楚的表达出自己的功能,变量名称是代码清晰的关键。(p15)4.绝大多数情况下,函数应该放在它使用的数据所属的对象内。(p17)5.有时候我会保留旧函数,让它调用新函数。如果旧函数式一个public函数,而我又不想修改其他类接口,这便是一种有用的手法。(p20)6.最好不要再另一个对象的属性基础上运用switch语句。如果不得不使用,也应该在对象自己的数据上使用,而不是在别人的数据上使用。(p34)7.阅读全文