# 原则19:确保0是值类型的一个有效状态
**By D.S.Qiu**
**尊重他人的劳动,支持原创,转载请注明出处:[http://dsqiu.iteye.com](http://dsqiu.iteye.com)**
.NET 系统会将所有对象默认初始化为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
// First edition included Pluto.
}
Planet sphere = new Planet();
```
sphere 等于0,这不是有效值。任何代码都依赖于(正常)的事实,枚举类型都限定于定义的枚举集,所以就不能工作。如果你创建一个枚举类,确保0是其中一个值。如果你在你的枚举使用位模式,将0定义为所有其他属性都缺失。
既然如此,你可以强迫所有使用者都显示初始化值:
```
Planet sphere2 = Planet.Mars;
```
但很难构建包含这个类型的其他类型:
```
public struct ObservationData
{
private Planet whichPlanet; //what am I looking at?
private double magnitude; // perceived brightness.
}
```
使用者创建 ObservationData 对象会创建无效的 Planet 域:
```
ObservationData d = new ObservationData();
```
新创建的 ObservationData 有0的 magnitude,这是合理的。但是 wihchPlanet 是无效的。你需要使0是一个合理的值。如果可能,最好选择0为作为默认值。 Planet 枚举没有任何明显的默认值。当使用者没有指定默认值,它不会随意选择一个枚举值。如果你遇到这种情况,使用0作为未初始状态然后在后面更新:
```
public enum Planet2
{
None = 0,
Mercury = 1,
Venus = 2,
Earth = 3,
Mars = 4,
Jupiter = 5,
Saturn = 6,
Neptune = 7,
Uranus = 8
}
Planet sphere = new Planet();
```
sphere 现在的值是空。给 Planet 枚举加上未初始化的默认值会扩散到 ObservationData 结构体中。这样新创建的 ObservationData 有一个0 magnitude 和空为目标。添加显示构造函数让使用者显示初始化所有域的值:
```
public struct ObservationData
{
Planet whichPlanet; //what am I looking at?
double magnitude; // perceived brightness.
ObservationData(Planet target,
double mag)
{
whichPlanet = target;
magnitude = mag;
}
}
```
但是记住默认构造函数仍可见的还是构造函数的一部分。使用者还是可以创建系统初始化的变量,并且你不能阻止他们。
这仍然有些错误,因为没有真的观察是没有任何意义。你可以通过改变 Observation 为类解决这个特例,这样无参构造函数就不可用了。但是,如果你创建枚举,你不可能强制其他开发者遵从这个规则。最好的最好是创建的枚举类型的0位模式是有效的,即使这不是一个完美的抽象。
在讨论其他类型之前,你需要理解使用 enum 作为标记的一些特殊例子。 enum 使用标记特性需要总是设置 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();
```
如果你使用标记位,确保0是有效的,它表示“没有任何标记”。
另一个常见初始化问题涉及到值类型包含引用类型。string 是常见的例子:
```
public struct LogMessage
{
private int ErrLevel;
private string msg;
}
LogMessage MyMessage = new LogMessage();
```
MyMessage 包含为 null 的 msg 域引用。没有任何方式去强制不同的初始化,但是你可以使用属性本地化这个问题。你可以创建一个属性想所有使用者暴露 msg 的值。添加逻辑使得属性返回空字符串而不是 null:
```
public struct LogMessage2
{
private int ErrLevel;
private string msg;
public string Message
{
get
{
return (msg != null) ? msg : string.Empty;
}
set
{
msg = value;
}
}
}
```
你应该在你的类的内部使用这个属性。总是只在一个地方检查 null 引用。当你从你的程序集中调用 Mesaage 的访问也是 inline 的。你会活得高效的代码和最少的错误。
系统会默认初始化所有实例值为0。没有任何方法阻止使用者创建值类型的实例不都是0。如果可能,使得所有0都是自然默认值。一个特殊情况是,enum 作为标记位使用应该确保0表示没有任何标记位。
小结:
主要是 struct 和 enum 值类型的默认初始化,要能保证0值时一个有效的值。
欢迎各种不爽,各种喷,写这个纯属个人爱好,秉持”分享“之德!
有关本书的其他章节翻译请[点击查看](/category/297763),转载请注明出处,尊重原创!
如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。
转载请在**文首**注明出处:[http://dsqiu.iteye.com/blog/2079730](/blog/2079730)
更多精彩请关注D.S.Qiu的博客和微博(ID:静水逐风)
- 第一章 C# 语言习惯
- 原则1:使用 属性(Poperty)代替可直接访问的数据成员(Data Member)
- 原则2:偏爱 readonly 而不是 const
- 原则3:选择 is 或 as 而不是强制类型转换
- 原则4:使用条件特性(conditional attribute)代替 #if
- 原则5:总是提供 ToString()
- 原则6:理解几个不同相等概念的关系
- 原则7:明白 GetHashCode() 的陷阱
- 原则8:优先考虑查询语法(query syntax)而不是循环结构
- 原则9:在你的 API 中避免转换操作
- 原则10:使用默认参数减少函数的重载
- 原则11:理解小函数的魅力
- 第二章 .NET 资源管理
- 原则12:选择变量初始化语法(initializer)而不是赋值语句
- 原则13:使用恰当的方式对静态成员进行初始化
- 原则14:减少重复的初始化逻辑
- 原则15:使用 using 和 try/finally 清理资源
- 原则16:避免创建不需要的对象
- 原则17:实现标准的 Dispose 模式
- 原则17:实现标准的 Dispose 模式
- 原则18:值类型和引用类型的区别
- 原则19:确保0是值类型的一个有效状态
- 原则20:更倾向于使用不可变原子值类型
- 第三章 用 C# 表达设计
- 原则21:限制你的类型的可见性
- 原则22:选择定义并实现接口,而不是基类
- 原则23:理解接口方法和虚函数的区别
- 原则24:使用委托来表达回调
- 原则25:实现通知的事件模式
- 原则26:避免返回类的内部对象的引用
- 原则27:总是使你的类型可序列化
- 原则28:创建大粒度的网络服务 APIs
- 原则29:让接口支持协变和逆变
- 第四章 和框架一起工作
- 原则30:选择重载而不是事件处理器
- 原则31:用 IComparable<T> 和 IComparer<T> 实现排序关系
- 原则32:避免 ICloneable
- 原则33:只有基类更新处理才使用 new 修饰符
- 原则34:避免定义在基类的方法的重写
- 原则35:理解 PLINQ 并行算法的实现
- 原则36:理解 I/O 受限制(Bound)操作 PLINQ 的使用
- 原则37:构造并行算法的异常考量
- 第五章 杂项讨论
- 原则38:理解动态(Dynamic)的利与弊
- 原则39:使用动态对泛型类型参数的运行时类型的利用
- 原则40:使用动态接收匿名类型参数