Effective C# Item 8 : Ensure That 0 Is a Valid State for Value Types
.Net中默认的初始化是将对象的所有信息都置0。对于值类型来说,我们不能避免在创建新的值类型实例的时候将其值置为0。这是类型的默认值。
唯一一个特殊情况是枚举(enum)。我们不应该创建一个不包含0作为其值的枚举。所有的枚举都继承自System.ValueType。枚举值是从0开始的,但是我们可以修改它:(P.S. 虽然现在Pluto已经不是太阳系的一员了,bless一下)
{
Mercury = 1,
Venus = 2,
Earth = 3,
Mars = 4,
Jupiter = 5,
Saturn = 6,
Neptune = 7,
Uranus = 8,
Pluto = 9
}
Planet sphere = new Planet();
现在sphere的值就是0,这不是一个合法的值。虽然不会报错,但是0并不代表任何含义。当我们创建自己的引用时,应当确保0是其中的一个枚举值。如果你将枚举声明为二进制类型的,需要定义0来代表None。
如果这样发展下去的话,我们只能强迫用户在初始化的时候就为其明确赋值。
这将使得我们很难在其他的类型中使用这种类型,因为我们并不知道要将其初始化为何值。
使用这样的类型创建对象将会创建一个不合法的Planet。
我们必须确保0是一个有效的值。如果可以的话应当将其对应为为最佳默认值。对于Planet枚举来说,没有什么最佳可言。当用户没有使用的时候,它应当不包含任何含义。我们可以使用0来改造我们的枚举使其更加合理:
{
None = 0,
Mercury = 1,
Venus = 2,
Earth = 3,
Mars = 4,
Jupiter = 5,
Saturn = 6,
Neptune = 7,
Uranus = 8,
Pluto = 9
}
Planet sphere = new Planet();
sphere现在对应的枚举值是None,代表还没有用户数据。在这里添加默认值对ObservationData造成的影响就是新初始化的对象的枚举值是0,代表的含义是None。之后可以在ObservationData的构造函数中对其进行修改。要注意的是默认的构造函数仍然有效。用户可以构造一个默认值的对象,我们不能阻止他们这样做。
在我们结束enum的讨论之前,我们还有必要说一下在使用flag的时候枚举的一些特殊情况。使用Flags属性时我们应当将代表None含义的枚举值设为0:(P,S.关于Flags枚举的情况请看我写的另一篇介绍:FlagsAttribute属性在enum中的应用)
public enum Styles
{
None = 0,
Flat = 1,
Sunken = 2,
Raised = 4
}
许多开发者通过二进制操作符&来使用flags枚举。不合理的0值会造成严重的问题,如果flag为0值则下例中的条件会永远不能成立:
DoSth(); // 如果flag为0则永远不会执行
因此如果使用Flags,我们应当确定0代表的含义是None。
另外的常见初始化问题发生在值类型中包含的引用类型上。String就是一个常见的例子(P.S. .Net中string是引用类型,虽然某些操作符的重载会给用户带来值类型的错觉)。
{
private int _ErrLevel;
private string _msg;
}
LogMessage MyMessage = new LogMessage();
MyMessage包含了一个为null的引用类型。这样并不会引起任何问题。但是当你使用属性来获取它的值的时候就可能会出现问题。因此我们要为其添加如果为null则返回string.Empty的逻辑。在我们的结构中我们应当这样检验属性是否为null。
{
private int _ErrLevel;
private string _msg;
public string Message
{
get
{
return(_msg != null)? _msg:string.Empty;
}
set
{
_msg = value;
}
}
}
系统初始化时所有的类型的值都为0,这是不能避免的。可能的话应当将0设为最初的默认值。特殊情况下,例如flags枚举,应当让其代表None。
译自 Effective C#:50 Specific Ways to Improve Your C# Bill Wagner著
回到目录