美文网首页.NET程序员dotNET
[C#] const vs. static readonly

[C#] const vs. static readonly

作者: 丑小丫大笨蛋 | 来源:发表于2016-05-28 13:42 被阅读499次

    前段时间写code的时候需要在类中定义一个常量的字符串,我就随手写了个const string = "xxx";。结果review别人的code时发现他们用的时static readonly,看起来效果差不多,那么究竟该用哪个呢?于是,我先把我们整个大工程里的code大概翻了翻,想看看大家都是怎么用的以及这两种有没有什么适用环境,结果是太混乱了,相同的情况下用这两个的都有。所以,我决定梳理一下这两个字段。

    1. const与static readonly的最主要区别

    我觉得conststatic readonly最大的区别在于,前者是静态常量,后者是动态常量。意思就是const在编译的时候就会对常量进行解析,并将所有常量出现的地方替换成其对应的初始化值。而动态常量static readonly的值则在运行时才拿到,编译的时候只是把它标识为只读,不会用它的值去替换,因此static readonly定义的常量的初始化可以比const稍微推迟一些。

    为了更清楚得看到编译时获取值与运行时获取值的区别,这里有一个简单的例子。
    我们写新建一个名为ConstStaticReadOnlyConsole Application Project和一个名为MyClassConfigPortable Class Library Project

    // ConstStaticReadOnly Project
    namespace ConstStaticReadOnly
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("const value is: {0}", MyClassConfig.ConstValue);
                Console.WriteLine("static readonly value is: {0}", MyClassConfig.ReadonlyValue);
            }
        }
    }
    
    // MyClassConfig Project
    namespace ConstStaticReadOnly
    {
        public class MyClassConfig
        {
            public const string ConstValue = "const";
            public static readonly string ReadonlyValue = "readonly";
        }
    }
    

    编译运行我们可以看到下面的输出结果:

    const value is: const
    static readonly value is: readonly
    

    下面我们修改一下两个常量的值 (后面都加上value),然后只把MyClassConfig重新编译一遍,并将生成的dll拷贝到ConstStaticReadOnlybuild目录下替换原来的那个dll,再运行ConstStaticReadOnly则可以得到下面的输出:

    const value is: const
    static readonly value is: readonly value
    

    从运行结果可以看到,只有static readonly那个值变了,const还是原来的值。这是因为我们没有重新build ConstStaticReadOnly工程,而它里面用到的ConstValue值早在上次build的时候就已经被替换成了"const"。那么,怎么才能把ConstStaticReadOnly里面的值变成最新的呢?很简单,在ConstValue值修改以后,重新build ConstStaticReadOnly

    这样一下子就可以看到在这里使用const的缺点了,如果我们的MyClassConfig被其他100个工程引用的话,每次修改MyClassConfig后一定要重新build这100个工程,不然的话这些工程里的const值就不会更新。

    当然上面的例子并不是说const不好或者我们不要用const,只是说有些情况不适合用const,而且const也有自身的优点,如编译时就被解析从而免去了运行时的一些调用,既可以声明在类中也可以声明在函数体内等。
    下面我们就来分析一下两者分别适用的情况。

    2. 什么时候用const

    (1)对于我们非常确定不会改变的常量,(这里的改变不是指运行时试图重新赋值来改变,而是指code中写的那个值被修改)例如:
    const int CM_IN_A_METER = 100;

    (2)在函数体内声明的常量,例如:

    void func()
    {
        const double PI = 3.14;
        // use PI to do some calculation
    }
    

    (3)用于attribute里,例如

    public static class Text 
        {
            public const string ConstDescription = "This can be used.";
            public readonly static string ReadonlyDescription = "Cannot be used.";
        }
    
        public class Fun 
        {
            // You should add using System.ServiceModel.Description (System.ServiceModel.dll);
            // and using System.ComponentModel (System.dll);
            [Description(Text.ConstDescription)]
            public int BarThatBuilds { get; set; }
    
            [Description(Text.ReadOnlyDescription)]
            public int BarThatDoesNotBuild { get; set; }
        }
    

    attribute里面只能使用const常量,使用static readonly会出现编译错误。

    Error   1   'ConstStaticReadOnly.Text' does not contain a definition for 'ReadOnlyDescription'
    

    (4)当你需要implicit conversion时
    下面是stackoverflow上有人提供的一个例子,采用conststatic readonly得到的结果会不一样。

    const int y = 42;
    static void Main()
    {
        short x = 42; 
        Console.WriteLine(x.Equals(y)); // True
    }
    
    static readonly int y = 42;
    static void Main()
    { 
        short x = 42; 
        Console.WriteLine(x.Equals(y)); // False
    }
    

    The reason is that the method x.Equals has two overloads, one that takes in a short (System.Int16) and one that takes an object (System.Object). Now the question is whether one or both apply with my y argument.

    对于const修饰的int常量情况,存在implicit conversion from int to short,这样比较的时候就使用了short版本的Equals;而static readonly修饰的int则不具有隐士转换的功能,比较的时候使用的objectEquals,如果你认为这种情况下他们应该相等,则可以在比较的时候进行显示转换,如x.Equals((short)y)

    3. 什么时候用static readonly

    (1)需要根据config文件里的值来初始化的
    为了方便管理常量,我们通常会把一个project或者solution里的所有常量集中起来,采用config文件进行配置。这样不仅便于管理、修改和维护,而且可以在不同的环境下使用不同的config文件来初始化code里的那些常量。const修饰的常量必须在声明的时候就初始化在code里,肯定是做不到这一点的,所以可以采用static readonly来声明这些常量,然后在构造函数里load config文件,对所有相应的常量进行初始化。

    (2)可能会发生变化的常量
    其实(1)也可以看做是这一类,只是我觉得(1)比较常用,而且像(1)那样对常量进行集中管理是一种很好的习惯,所以才单独提出来了。下面来对可能发生变化的常量举一个例子,

    class MyMathLib
    {
        private static readonly PI = 3.14;
    }
    

    为什么说PI是一个可能会变得常量呢?因为不同情况下你的工程对精度的要求可能不一样,某天如果突然间发现只保留两位小数时精度不够时,可能就会把它改成3.14159了。另外,这里的PI跟上面函数体内需要用到的PI必须用const并不矛盾,虽然函数体内的PI也可能会改变,但是并不要紧,因为它已经在函数体内了,改变后肯定会同时编译PI常量和那个函数。

    (3)需要new操作符初始化的
    const一般用于修饰值类型或者string(注意string是引用类型)。因为引用类型(除了string)是要通过new关键字来初始化的,而const声明的常量是不能用new来初始化的,所以如果你一定要用const来修饰一个引用类型(string除外)的常量,请初始化为null。例如,Fun f = new Fun();会引起下面的编译错误:

    Error   1    A const field of a reference type other than string can only be initialized with null.
    

    所以,如果你要将引用类型的非空值定义为常量,你需要使用static readonly

    private static readonly List<int> test = new List<int> {1, 2, 3};
    

    (4)关于private与public
    类中static readonly修饰的常量应该用private还是public呢?如果用private,那客户端那边就不能直接访问了,所以就定义成public?对于一般的值类型或者string,定义成public static readonly当然没问题,这也是我们常用的。

    可是对下面一种情况可能会有问题:

    // ConstStaticReadOnly Project
    namespace ConstStaticReadOnly
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("x={0}, y={1}", MyClassConfig.ReadonlyPoint.x, MyClassConfig.ReadonlyPoint.y);
                 // MyClassConfig.ReadonlyPoint = new Point() is not allowed
                // We cannot change the reference of ReadonlyPoint
                // But we can change the fields in ReadonlyPoint
                MyClassConfig.ReadonlyPoint.x = 3;
                MyClassConfig.ReadonlyPoint.y = 4;
                Console.WriteLine("x={0}, y={1}", MyClassConfig.ReadonlyPoint.x, MyClassConfig.ReadonlyPoint.y);
            }
        }
    }
    
    // MyClassConfig Project
    namespace ConstStaticReadOnly
    {
        public class Point
        {
            public int x;
            public int y;
            public Point(int a, int b)
            {
                x = a;
                y = b;
            }
        }
    
        public class MyClassConfig
        {
            public static readonly Point ReadonlyPoint = new Point(1, 2);
        }
    }
    

    输出结果:

    x=1, y=2
    x=3, y=4
    

    我们的本意应该是让ReadonlyPoint不能被外界改变,现在看来上面的static readonly并没有达到这个效果。这是因为static readonly修饰的常量只能保证reference不能变,也就是不能对ReadonlyPoint进行重新赋值,但是ReadonlyPoint引用的那个Point里面的值是可以被改变的,这叫mutable reference types

    所以在用FxCop 对代码进行分析时,会出现Do not declare read only mutable reference types的warning。也就是说上面那样用public static readonly修饰的ReadonlyPoint并不是安全的,下面有一种解决方案:
    ReadonlyPoint声明为private或者protected,然后提供一个仅提供get函数的property来返回内部的ReadonlyPoint

    protected static readonly Point readonlyPoint = new Point(1, 2);
    
    public static Point ReadonlyPoint
    {
        get
        {
            return readonlyPoint;
        }
    }
    

    4. 小结

    (1)const常量在编译时解析;而static readonly常量在运行时解析。
    (2)const常量必须在定义时初始化;而static readonly常量可以在定义时初始化,也可以在构造函数中初始化;
    (3)非常确定不会改变的常量值可以用const,必须写在函数体内的常量需要用const,需要被attributes用到的常量应该用const
    (4)常量需要被客户端引用,且可能会改变,应该用static readonly

    参考文献:

    1. const (C# Reference)
    2. readonly (C# Reference)
    3. What is the difference between const and readonly?
    4. Static readonly vs const
    5. const和readonly区别
    6. Do not declare read only mutable reference types

    相关文章

      网友评论

        本文标题:[C#] const vs. static readonly

        本文链接:https://www.haomeiwen.com/subject/jvpfdttx.html