Effective C# 原则8:确保0对于值类型数据是有效的

JerryXia 发表于 , 阅读 (811)

.Net系统默认所有的对象初始化时都为0。这并没有提供一个方法来预防其他程序员创建的值类型数据的实例在初始化是都是0。请让你的数据类型默认值也是0。

一个特殊情况是在枚举类型数据中。决不要创建一个不包括0在内的枚举类型。所有的枚举类型都是从System.ValueType派生的。枚举类型的值是从0开始的,但你可以改变这一行为:

public enum Planet
{
    // Explicitly assign values.
    // Default starts at 0 otherwise.
    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是当中的一个。如果你的枚举类型采用的是以位(bit)模式,把0定义为其它属性不存在时的取值。
按照现在的情况,你迫使用户必须精确的初始化值:Planet sphere =
Planet.Mars;这将使包含(Planet)这一类型的其它类型很难创建:

public struct ObservationData
{
    Planet   _whichPlanet; //what am I looking at?
    Double  _magnitude; // perceived brightness.
}

创建一个新ObservationData实例的用户会创建一个不合法的Planet成员:ObservationData
d = new
ObservationData();最后创建的ObservationData的成员_magnitude的值是0,这是合理的。但_whichPlanet却是无效的。你须要让0也是有效的(状态)。如果可能,选择把0做为一个最好的默认。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对象的目标上具有None和一个数值0。添加一个清晰的构造函数让用户为你的类型的所有字段明白的初始化:

public struct ObservationData
{
    Planet   _whichPlanet; //what am I looking at?
    Double  _magnitude; // perceived brightness.

    ObservationData( Planet target, Double mag )
    {
    _whichPlanet = target;
    _magnitude = mag;
    }
}

但请记住,默认的构造函数还是可访问的,而且是结构的部份。用户还是可以创建一个系统初始化的变量,而你无法阻止它。

在结束枚举类型转而讨论其它类型之前,你须要明白几个用于标记的特殊枚举类型规则。枚举类型在使用Flags特性时,必须把None的值设置为0:

[Flags]
public enum Styles
{
    None = 0,
    Flat = 1,
    Sunken = 2,
    Raised = 4
}

很多开发人员使用枚举标记和位运算操作AND进行运行,0值会与位标记产生严重的问题。下面这个实验如果Flat的值是0时,是决不会成功的:

if ( ( flag & Styles.Flat ) != 0 ) // Never true if Flat == 0.
    DoFlatThings();

如果你遇到Flags,确保0对它来说是有效的,并且这就着:“对所有缺少的标记。”

另一个很常见的初始化问题就是值类型中包含了引用类型。字符串是一个常见的例子:

public struct LogMessage
{
    private int _ErrLevel;
    private string _msg;
}

LogMessage MyMessage = new LogMessage( );

MyMessage包含了一个_msg为null的引用字段。这里没有办法强行使用另一个不同的初始化方法,但你利用属性来局部化这个问题。你创建一个属性向所用的用户暴露_Msg的值。添加一个业务逻辑,使得当字符串为null引用是,用空
串来取而代之:

public struct LogMessage
{
    private int _ErrLevel;
    private string _msg;

    public string Message
    {
        get
        {
            return (_msg != null ) ? _msg : string.Empty;
        }
        set
        {
            _msg = value;
        }
    }
}

(译注:我个人觉得这里违反了原则一。当对两个实例进行赋值COPY时,会出现,你明明使用了a=b的运行,但实际上a!=b的结果。可以参见原则1。)

在你自己的数据类型内部,你应该添加这样的一个属性。做了这样的局部处理后,null引用在某一位置做了验证。当调用是在你的程序集内时,Message的访问器基本上是可以很好的内联的。你将会取得高效低错的代码。

系统为所有的值类型数据初始化为0,而没有办法防止用户在创建一个值类型实例时,给所有的值类型都赋值为0。如果可能,把0设置为自然的默认值。特殊情况下,使用Flags特性的枚举类型必须确保0是所有缺省标记的值。

添加新评论