.NET系统的默认初始化机制会将所有的对象设置为0[14]。对于值类型来讲,我们无法阻止其他程序员将其所有的成员都初始化为0[15]。因此,我们应该将0作为值类型的默认值。 枚举类型就是一种典型的情况。我们创建的枚举类型决不应该将0视为无效状态。我们知道,所有的枚举类型都继承自System.ValueType。默认的枚举值从0开始,但是我们可以更改这种默认行为。 public enum Planet { // 显式赋值。 // 否则将默认从0开始。 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定义为“不包括所有其他属性的情况”。 根据目前的情况来看,我们应该强制用户显式初始化枚举值: Planet sphere = Planet.Mars; 但这将使得这样的枚举类型很难作为值类型的成员: public struct ObservationData { Planet _whichPlanet; // 看的是什么呢? Double _magnitude; // 感觉亮度。 } 创建ObservationData对象将得到一个无效的Planet字段: ObservationData d = new ObservationData(); 新创建的ObservationData对象的_magnitude将为0,这是合理的。但_whichPlanet却是无效的。我们需要让0成为有效的状态。如果可能的话,我们最好将0作为默认的值。Planet枚举类型没有一个明显的默认值。当用户没有给出明确的选择时,我们随便设定一个Planet值是没有意义的。如果碰到这种情况,我们可以将0作为一个未初始化值明确表示出来,这样可方便后续再对其更新: public enum Planet { 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值。将这个未初始化的默认值添加到Planet枚举中,会给ObservationData结构带来一些影响。新创建的ObservationData对象将包含一个值为0的_magnitude和一个值为None的_whichPlanet。这时候,我们应该添加一个显式的构造器,来支持用户显式初始化类型所有的字段: public struct ObservationData { Planet _whichPlanet; // 看的是什么呢? Double _magnitude; // 感觉亮度。 ObservationData( Planet target, Double mag ) { _whichPlanet = target; _magnitude = mag; } } 但是,要记住ObservationData仍然有一个默认构造器。用户仍可以使用默认的构造器来创建“让系统初始化”的变量,我们无法禁止用户这么做。 在讨论其他值类型之前,我们需要再谈一下枚举类型作为位标记(flag)来应用时的一些特殊规则。使用Flags特性的枚举类型应该总是将None值设为0: [Flags] public enum Styles { None = 0, Flat = 1, Sunken = 2, Raised = 4, } 许多开发人员都在位标记枚举值上使用“按位AND”(bitwise AND)操作符。如果遇到0值,就会出现严重的问题。如果Flat值为0,那么下面的测试将永远为false: if ( ( flag & Styles.Flat ) != 0 ) // 如果Flat == 0,将永远为false。 DoFlatThings( ); 如果使用Flags,我们要确保0为有效状态,且其意义为“不包括所有其他标记的情况”。 如果值类型中包含有引用类型,会出现另一种常见的初始化问题。包含字符串就是一种常见的情况: public struct LogMessage { private int _ErrLevel; private string _msg; } LogMessage MyMessage = new LogMessage( ); MyMessage 对象的_msg字段将为一个空引用。我们没有办法强制做其他的初始化,但是我们可以使用属性来将该问题限定在类型内部。我们可以创建一个属性来将_msg 值暴露给类型的所有客户,并在属性内部添加逻辑,使其返回一个“内容为空的字符串”,而非一个空引用: public struct LogMessage { private int _ErrLevel; private string _msg; public string Message { get { return (_msg != null )? _msg : string.Empty; } set { _msg = value; } } } 我们应该在类型内部使用这样的属性。这样做可以将空引用检查集中在一个地方。当从我们的程序集中被调用时,Message的访问器方法几乎肯定会被内联。我们在获得高效代码的同时,也将错误降到了最低。 综上所述,系统会将值类型的所有实例初始化为0。我们没有办法阻止用户创建“字段全部为0”的值类型实例。如果可能的话,我们应该将“字段全部为0”作为类型的默认值。作为一种特殊情况,被用做位标记的枚举类型,应该确保0的意义为“不包括所有其他标记的情况”。 |
|
来自: 兰亭文艺 > 《改善C#程序的50种方法》