《Effective C#》条款8:确保0为值类型的有效状态

.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的意义为“不包括所有其他标记的情况”。

时间: 2025-01-01 11:05:35

《Effective C#》条款8:确保0为值类型的有效状态的相关文章

EffectiveC#8--确保0对于值类型数据是有效的(初始化问题)

1.决不要创建一个不包括0在内的枚举类型 2.举例如下: public enum Planet { Mercury = 1, Venus = 2, Earth = 3, Mars = 4, Jupiter = 5, Saturn = 6, Neptune = 7, Uranus = 8, Pluto = 9 } Planet sphere = new Planet(); sphere此时的值就是0,而这并不是一个有效的值.这也会使得包含(Planet)这一类型的其它类型很难创建. 假设某个结构体

传智的光辉岁月-C#基础篇五值类型和引用类型

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace P01Method { class Program { static void Main(string[] args) { //int a1 = 11; //int b2 = 22; //Add2Num(a1, b2);//在调用方法时,为 方法括号中 传递的 值 就叫做 实参(实际参数) //Add2Nu

More Effective C++ 条款0,1

More Effective C++ 条款0,1 条款0 关于编译器 不同的编译器支持C++的特性能力不同.有些编译器不支持bool类型,此时可用 enum bool{false, true};枚举类型来模拟bool类型.这允许参数类型为int和bool的函数重载,但是这样做的缺陷是,对于内置的比较运算符,其仍返回int类型. f(int);f(bool); f(a < b); // 会调用f(int),但其实用户期望调用f(bool). 但是一旦改用支持bool类型的编译器,情况可能会发生改变

Effective C++ 条款3 尽可能用const

1. const可被施加于任何作用域内的对象,函数参数,函数返回类型,成员函数本体.用const修饰指针,如果const出现在*之前,表明指针不能更改所指向的对象的内容,如果const出现在*之后,表明指针只能指向同一块内存.另外int const*p和const int*p含义相同.如果对象成员有普通指针,那么构造该类的一个const对象时,const修饰使得该指针只能指向同一块内存,但指针指向的内容可以改变. 2. 将某些东西声明为const可以帮助编译器侦测出错误用法. 3. 编译器强制实

More Effective C++ 条款35 让自己习惯于标准C++ 语言

(由于本书出版于1996年,因此当时的新特性现在来说可能已经习以为常,但现在重新了解反而会起到了解C++变迁的作用) 1. 1990年后C++的重要改变 1). 增加了新的语言特性:RTTI,namespaces,bool,关键词mutable和explicit,enums作为重载函数之自变量所引发的类型晋升转换,以及"在class 定义区内直接为整数型(intergral) const static class members设定初值"的能力. 2). 扩充了Templates的特性

Effective C++ 条款11,12 在operator= 中处理&ldquo;自我赋值&rdquo; || 复制对象时不要忘记每一个成分

1.潜在的自我赋值     a[i] = a[j];     *px = *py; 当两个对象来自同一个继承体系时,他们甚至不需要声明为相同类型就可能造成别名. 现在担心的问题是:假如指向同一个对象,当其中一个对象被删,另一个也被删,这会造成不想要的结果. 该怎么办? 比如:   widget& widget:: operator+ (const widget& rhs) {    delete pd;    pd = new bitmap(*rhs.pb);    return *thi

Effective C++ 条款13/14 以对象管理资源 || 在资源管理类中小心拷贝行为

三.资源管理       资源就是一旦你使用了它,将来不用的时候必须归还系统.C++中最常用的资源就是动态内存分配.其实,资源还有 文件描述符.互斥器.图形界面中的字形.画刷.数据库连接.socket等. 1.        以对象管理资源       void f() {     investment *plv = createInvestment();     //这里存在很多不定因素,可能造成下面语句无法执行,这就存在资源泄露的可能.     delete plv; }      这里我们

effective c++ 条款3 use const whereever you can

1 const 传达的意思应该是这个变量是常量不能更改 2 const 在 * 左边表示数据是const,在右边表示指针是const // char greeting[] = "hello"; char* p = greeting; //const *: const data //* const: const pointer char const *p1 = greeting; // const data char * const p2 = greeting; // const poi

effective c++ 条款9 do not call virtual function in constructor or deconstructor

在构造函数中不要调用virtual函数,调用了也不会有预期的效果. 举个例子 class Transaction { public: Transaction() { log(); } virtual void log() =0; } class BusinessTransaction: public Transaction { public: virtual void log() { ;//log something here } } BusinessTransaction b_trx; b_t