编程语言

img MaybeHelios

Effective C#之8:Ensure That 0 Is a Valid State for Value Types

发表于2008/10/3 20:45:00  710人阅读

分类: Effective C#

Item8  Ensure That 0 Is a Valid State for Value Types

对于值类型,确保0是一个有效的状态

The default .NET system initialization sets all objects to all 0s. There is no way for you to prevent other programmers from creating an instance of a value type that is initialized to all 0s. Make that the default value for your type.

默认的.Net系统初始化机制将所有的对象都设置为0。对于你来说,没有办法阻止其他程序员来创建一个被设置成全0的值类型的实例。将0定义为你的值类型的默认值。

One special case is enums. Never create an enum that does not include 0 as a valid choice. All enums are derived from System.ValueType. The values for the enumeration start at 0, but you can modify that behavior:

一个特殊的情况是枚举(enums),不要创建没把0做为有效选择的enums。所有的枚举类型都派生自System.ValueType。枚举类型的值是从0开始的,但是它可以被修改:

  1.     public enum Planet
  2.     {
  3.         //Explicitly assign values 显式赋值
  4.         //Default starts at 0 otherwise 否则将默认从0开始
  5.         Mercury = 1,
  6.         Venus = 2,
  7.         Earth = 3,
  8.         Mars = 4,
  9.         Jupiter = 5,
  10.         Saturn = 6,
  11.         Neptune = 7,
  12.         Uranus = 8,
  13.         Pluto = 9
  14.     }
  15.  
  16. Planet sphere = new Planet();

sphere is 0, which is not a valid value. Any code that relies on the (normal) fact that enums are restricted to the defined set of enumerated values won't work. When you create your own values for an enum, make sure that 0 is one of them. If you use bit patterns in your enum, define 0 to be the absence of all the other properties.

Sphere 0,并非是一个有效值。任何依赖于下面事实(通常都是这样的情况)的代码都不能工作:枚举值被限制在预定义的枚举值集合内。因此,当你为一个枚举创建自己的值的时候,确定0是其中的一个。如果在自己的枚举类型中使用位模式,将0定义为所有其他属性之外的情况。

As it stands now, you force all users to explicitly initialize the value:

根据目前的情况来看,你强迫所有的用户需要显式的初始化这个值:

  1. Planet sphere = Planet.Mars;

That makes it harder to build other value types that contain this type:

这使创建其它包含该类型的值类型变得很困难:

  1.     public struct ObservationData
  2.     {
  3.         Planet whichPlanet; //what am I looking at?
  4.         Double magnitude; // perceived brightness.
  5.     }

Users who create a new ObservationData object will create an invalid Planet field:

创建新ObservationData对象的用户将创建一个无效的Planet字段:

  1. ObservationData d = new ObservationData();

The newly created ObservationData has a 0 magnitude, which is reasonable. But the planet is invalid. You need to make 0 a valid state. If possible, pick the best default as the value 0. The Planet enum does not have an obvious default. It doesn't make any sense to pick some arbitrary planet whenever the user does not. When you run into that situation, use the 0 case for an uninitialized value that can be updated later:

新创建的ObservationData拥有一个为0magnitude,这是合理的。但是planet是无效的。你必须使0是一个有效的状态。如果可能的话,将默认的情况最好设置为0Planet枚举没有一个明显的默认值。当用户没有指定的时候就随便使用一些任意星球是说不过去的。当你遇到这种情况的时候,将0定义为一个未初始化的值,使得以后可以更新。

  1.     public enum Planet
  2.     {
  3.         None = 0,
  4.         Mercury = 1,
  5.         Venus = 2,
  6.         Earth = 3,
  7.         Mars = 4,
  8.         Jupiter = 5,
  9.         Saturn = 6,
  10.         Neptune = 7,
  11.         Uranus = 8,
  12.         Pluto = 9
  13.     }
  14. Planet sphere = new Planet();

sphere now contains a value for None. Adding this uninitialized default to the Planet enum ripples up to the ObservationData structure. Newly created ObservationData objects have a 0 magnitude and None for the target. Add an explicit constructor to let users of your type initialize all the fields explicitly:

Sphere现在包含一个None值。向Planet枚举中加入这个未初始化的默认值,使ObservationData结构起了变化。新创建的ObservationData对象,包含有一个0magnitude值,和一个NonewhichPlanet值。加入显式的构造器来让你的类型的用户可以显式的初始化所有的字段:

  1.     public struct ObservationData
  2.     {
  3.         Planet whichPlanet; //what am I looking at?
  4.         Double magnitude; // perceived brightness.
  5.  
  6.         ObservationData(Planet target,Double mag)
  7.         {
  8.             whichPlanet = target;
  9.             magnitude = mag;
  10.         }
  11.     }

But remember that the default constructor is still visible and part of the structure. Users can still create the system-initialized variant, and you can't stop them.

但是请记住,默认的构造器仍然是可见的,它是该结构的一部分。用户仍然可以创建由系统进行初始化的变量,而且你无法阻止。

Before leaving enums to discuss other value types, you need to understand a few special rules for enums used as flags. Enums that use the Flags attribute should always set the None value to 0:

在离开enums类型去讨论其他值类型之前,你需要明白对于enums具有一些特殊的规则,像作为位标记的Flags。使用Flags特性的enums应该始终将None值设置为0

  1.     [Flags]
  2.     public enum Styles
  3.     {
  4.         None = 0,
  5.         Flat = 1,
  6.         Sunken = 2,
  7.         Raised = 4,
  8.     }

Many developers use flags enumerations with the bitwise AND operator. 0 values cause serious problems with bitflags. The following test will never work if Flat has the value of 0:

多数开发人员将带有Flags特性的枚举和位操作符AND一起使用。在位标记上,0值会引起严重的问题。在下面的测试中,如果Flat具有0值,测试将永远不能工作:

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

If you use Flags, ensure that 0 is valid and that it means "the absence of all flags."

如果使用Flags,就要确认0是有效并表示“不包括所有其他标记的情况”。

Another common initialization problem involves valuetypes that contain references. Strings are a common example:

另外一种初始化问题涉及到包含有引用的值类型。String是一个常见的例子:

  1.     public struct LogMessage
  2.     {
  3.         private int errLevel;
  4.         private string msg;
  5.     }
  6.  
  7.    LogMessage MyMessage = new LogMessage();

MyMessage contains a null reference in its _Msg field. There is no way to force a different initialization, but you can localize the problem using properties. You created a property to export the value of _Msg to all your clients. Add logic to that property to return the empty string instead of null:

MyMessagemsg字段上包含了一个空引用。没有办法强制一个不同的初始化,但是你可以使用属性来将该问题限制在类型内部。创建属性来向所有用户暴露msg的值,向该属性添加逻辑使其返回空字符串而不是null

  1.     public struct LogMessage
  2.     {
  3.         private Int32 errLevel;
  4.         private String msg;
  5.  
  6.         public String Message
  7.         {
  8.             get
  9.             {
  10.                 return (msg != null) ? msg : String.Empty;
  11.             }
  12.             set
  13.             {
  14.                 msg = value;
  15.             }
  16.         }
  17.     }

You should use this property inside your own type. Doing so localizes the null reference check to one location. The Message accessor is almost certainly inlined as well, when called from inside your assembly. You'll get efficient code and minimize errors.

应该在自己的类型内部使用该属性。这样做呢,可以让对null引用的检查集中在一个地方。当从程序集内部访问时,Message访问器几乎是内联的。这样,可以获得高效的代码和最小的错误。

The system initializes all instances of value types to 0. There is no way to prevent users from creating instances of value types that are all 0s. If possible, make the all 0 case the natural default. As a special case, enums used as flags should ensure that 0 is the absence of all flags.

系统将所有值类型的实例初始化为0。没有办法阻止用户创建全0的值类型实例。如果可能,就要将全0作为自然的默认值。作为一个特殊情况呢,被用作flagsenums应该保证0表示“不包括所有其他标记的情况”。

阅读全文
0 0

相关文章推荐

img
取 消
img