Admirer Of Nature

Finding Wonderland of .Net

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

Effective C#: Item 2 Prefer readonly to const

 

Item 2: 定义常量时,优先使用readonly,而不是const

 

C#中,有两种类型的常量,compile-timeruntime。它们的性质是差别很大的。正确的选择对于你程序的稳定性和性能都有很大的影响。Compile-time常量速度稍稍快一点,但灵活性非常的差,而且容易出错。当鱼和熊掌不可兼得的情况下,我们应该选择一个稍慢一点,但更加稳定和正确的方式来编写程序。只有当性能是程序决定因素的时候才使用Compile-time类型常量,它的值永远不变。

 

readonly 用来定义runtime类型常量;const 用来定义compile-time常量:

 

//compile time constant:

            public const int _Millennium = 2000;

 

            //runtime constant:

            public static readonly int _ThisYear = 2005;

 

compile-time常量和runtime常量性质的不同是由访问它们方式的不同决定的。Compile-time常量只是简单的值的替换。下面两段代码的IL code是相同的:

 

if( myDateTime.Year == _Millennium )

 

            if( myDateTime.Year == 2000 )

 

runtime常量只有在runtime才被解析。此时你reference的是一个read-only的常量,这个read-only的常量reference一个readonly的变量,而不是变量的值。

这种分别决定了你何时使用这两种类型的常量。Compile-time常量只能使用在primary类型上(比如built-in int, float)。因为只有这些类型才能在初始化时赋有意义的值。Compile-time常量是不能用new来赋值的:

 

//Does not compile, use readonly instead

            private const DateTime _classCreation =

                  new DateTime( 2000, 1, 1, 0, 0, 0 );

所以compile-time常量只能是数值和字符串。

Runtime常量的值在constructor运行后也是不可变的,但它们的赋值是在runtime进行的。所以也就有了更大的灵活性。runtime常量的值可以是任何类型,但你必须在constructorinitializer中给它们赋值。

你可以给实例常量赋readonly的值,这样类的每个实例可以有不同的值。当然你也可以把readonly常量定义为static. Compile-time常量则是static常量,定义是不用加static 的。

 

因为runtime常量只有在runtime才被解析。你所reference的是一个read-only的常量,这个read-only的常量reference一个readonly的变量,而不是变量的值。所以使用它可以给维护程序带来极大的便利。需要牢记的是,compile-time常量永远都是被值代替,即使是在一个assembly中定义,在另外一个assembly使用。这使得维护非常的不便。

 

比如你开发了一个assembly A,在这个assembly A中定义如下的两个常量:

 

public class UsefulValues

      {

            public static readonly int StartValue = 5;

 

            public const int EndValue = 10;

      }

 

在另外一个assembly B中你使用了这两个常量:

 

for( int i=UserfulValues.StartValue;

            i<UsefulValues.EndValue; i++)

      {

            Console.WriteLine( "Value is {0}", i );

      }

 

运行程序,你会得到如下输出:

 

            Value is 5

            Value is 6

Value is 9

 

过了一段时间后,你修改了assembly A

 

public class UsefulValues

      {

            public static readonly int StartValue = 105;

 

            public const int EndValue = 120;

      }

你发布了assembly A,但并没有重新编译整个程序,你认为你程序的输出会是:

            Value = 105

Value = 106

Value = 119

但实际情况并非如此。因为你没有重新编译,所以在assembly B中,StartValue变成了105,但EndValue仍然是10,所以你的程序不会输出任何东东。因为StartValue是在runtime解析的,所以不需重新编译整个程序,但EndValuecompile-time常量,所以没有变化。改变compile-time常量可以被看作是interface的改变,必须要重新编译程序中所有使用到它的code。但改变runtime常量的值可以被看作是implementation的改变,它具有binary的兼容性。也就是说改变interface的实现方法对于interface的用户是没有影响的,所以不用重新编译。你可以用reflector来观察IL code的不同。在IL中,StartValue是动态载入的,而EndValuehard-coded

但有些时候,你确实需要compile-time常量。比如在对一个object serialize时,你想用一些常量来描述object不同的版本信息,这些信息是永远不变的。但你需要一个runtime常量来描述当前版本信息。当前版本信息是随着新的release而改变的。

 

private const int VERSION_1_0 = ox0100;

      private const int VERSION_1_1 = ox0101;

      private const int VERSION_1_2 = ox0102;

 

      //major release:

      private const int VERSION_2_0 = ox0200;

 

      //check for the current version

      private static readonly int CURRENT_VERSION = VERSION_2_0;

 

      //read from file, check stored version

      //against compile-time constant

      protected MyType( SerializationInfo info,

StreamingContext cntxt )

      {

            int storedVersion = info.GetInt32( "VERSION" );

            switch( storedVersion )

            {

                  case VERSION_2_0:

                        ReadVersion2( info, cntxt );

                        break;

                  case VERSION_1_1:

                        ReadVersion1Dot1( info, cntxt );

                        break;

 

                  //...

            }

      }

 

      //write the current version

      [ SecurityPermissionAttribute( SecurityAction.Demand,

      SerilizationFormatter = true ) ]

      void ISerializable.GetObjectData( SerializationInfo inf,

      StreamingContext cxt )

      {

            //user runtime constant for current version

            inf.AddValue( "VERSION", CURRENT_VERSION );

           

            //...

}

因为runtime常量要进行一次reference访问,所以compile-time常量的效率稍稍好一点。但这是以牺牲 重要的灵活性为代价的。所以决定用const而放弃readonly之前,最好用profiler监测一下两种情况下的性能差别,以确定这样做确实值得。

总之,只有当你必须要在compile时确定值的情况下,比如修饰function内部的局部变量(readonly只可以用于修饰类的成员)attribute参数,枚举定义等等,才使用const。记住,这些值是永远不变的。其他一切情况下,使用readonly 最大的提高灵活性。

 

 

 

本系列文章只是作者读书笔记,版权完全属于原作者 (Bill Wagner),任何人及组织不得以任何理由以商业用途使用本文,任何对本文的引用和转载必须通知作者:zphillm@hotmail.com

posted on 2005-02-23 08:04  Admirer Of Nature  阅读(1571)  评论(4编辑  收藏  举报