zoukankan      html  css  js  c++  java
  • C#基本知识点-Readonly和Const的区别

    目录

    什么是静态常量(Const)和动态常量(Readonly)

      先解释下什么是静态常量(Const)以及什么是动态常量(Readonly)。
      静态常量(Const)是指编译器在编译时候会对常量进行解析,并将常量的值替换成初始化的那个值。
      动态常量(Readonly)的值则是在运行的那一刻才获得的,编译器编译期间将其标示为只读常量,而不用常量的值代替,这样动态常量不必在声明的时候就初始化,而可以延迟到构造函数中初始化。

    静态常量(Const)和动态常量(Readonly)之间的区别

     

    静态常量(Compile-time Constant)

    动态常量(Runtime Constant)

    定义

    声明的同时要设置常量值。

    声明的时候可以不需要进行设置常量值,可以在类的构造函数中进行设置。

    类型限制

    只能修饰基元类型,枚举类型或者字符串类型。

    没有限制,可以用它定义任何类型的常量。

    对于类对象而言

    对于所有类的对象而言,常量的值是一样的。

    对于类的不同对象而言,常量的值可以是不一样的。

    内存消耗

    无。

    要分配内存,保存常量实体。

    综述

    性能要略高,无内存开销,但是限制颇多,不灵活。

    灵活,方便,但是性能略低,且有内存开销。

    1. Const修饰的常量在声明的时候必须初始化;Readonly修饰的常量则可以延迟到构造函数初始化 。
    2. Const常量既可以声明在类中也可以在函数体内,但是Static Readonly常量只能声明在类中。Const是静态常量,所以它本身就是Static的,因此不能手动再为Const增加一个Static修饰符。
    3. Const修饰的常量在编译期间就被解析,即:经过编译器编译后,我们都在代码中引用Const变量的地方会用Const变量所对应的实际值来代替; Readonly修饰的常量则延迟到运行的时候。

      举个例子来说明一下:

            public static readonly int NumberA = NumberB * 10;
            public static readonly int NumberB = 10;
    
            public const int NumberC = NumberD*10;
            public const int NumberD = 10;
    
            static void Main(string[] args)
            {
                Console.WriteLine("NumberA is {0}, NumberB is {1}.", NumberA, NumberB);//NumberA is 0, NumberB is 10.
                Console.WriteLine("NumberC is {0}, NumberD is {1}.", NumberC, NumberD);//NumberC is 100, NumberD is 10.
                Console.ReadKey();
            }
    View Code

      以上是语法方面的应用,那在实际的用法上,还是有些微妙的变化,通常不易发觉.
      举个例子来说明一下:
      在程序集DoTestConst.dll 中有一个类MyClass,定义了一个公开的静态变量Count

        public static class MyClass
        {
            public const int Count = 10;
        }

      然后另外一个应用程序中引用DoTestConst.dll,并在代码中作如下调用:

        public static void Main(string[] args)
        {
            Console.WriteLine(DoTestConst.MyClass.Count);//输出10
            Console.ReadKey();
        }

      毫无疑问,非常简单的代码,直接输出10。
      接下来更新MyClass的Count的值为20,然后重新编译DoTestConst.dll,并更新到应用程序的所在目录中,注意不要编译应用程序。那么这时候的输出结果按预期那么想应该是20才对,但实际上还是10,为什么呢?
      这就是Const的特别之处,有多特别还是直接看生成的IL,查看IL代码(假设这时候Count的值为10)

      IL_0000: nop
      IL_0001: ldc.i4.s 10
      IL_0003: call void [mscorlib]System.Console::WriteLine(int32)

      红色代码很明显的表明了,直接加载10,没有通过任何类型的加载然后得到对应变量的,也就是说在运行时没有去加载DoTestConst.dll,那么是否意味着没有DoTestConst.dll也可以运行呢?答案是肯定的,删除DoTestConst.dll也可以运行,是否很诡异呢?也就解释了之前的实验,为什么更新Const变量的值之后没有调用新的值,因为程序在运行的时候根本不会去加载DoTestConst.dll。那么10这个值是从哪来的呢?实际上CLR对于Const变量做了特殊处理,是将Const的值直接嵌入在生成的IL代码中,在执行的时候不会再去从dll加载。这也带来了一个不容易发觉的Bug,因此在引用其他程序集的Const变量时,需考虑到版本更新问题,要解决这个问题就是把调用的应用程序再编译一次就ok了。但实际程序部署更新时可能只更新个别文件,这时候就必须用Readonly关键字来解决这个问题。

      接下来看Readonly的版本:

        public static class MyClass
        {
            public static readonly int Count = 10;
        }

      调用方代码不变,接着看生成的IL代码:

      IL_0000: nop
      IL_0001: ldsfld int32 [DoTestConst]DoTestConst.MyClass::Count
      IL_0006: call void [mscorlib]System.Console::WriteLine(int32)

      很明显加载代码变了,一个很常见的ldsfld动作,请求了DoTestConst.MyClass的Count变量,是通过强制要求加载DoTestConst来实现的。因此这时候更新Count的值重新编译之后,还是不编译调用程序,然后再执行就会看到新的值。而这时候如果删除DoTestConst.dll那么,会出现找不到dll之类的异常。这也充分说明了对于Readonly定义的变量是在运行时加载的。

    动态常量(Readonly)被赋值后不可以改变

      ReadOnly 变量是运行时变量,它在运行时第一次赋值后将不可以改变。其中“不可以改变”分为两层意思:

    1. 对于值类型变量,值本身不可以改变(Readonly, 只读)
    2. 对于引用类型变量,引用本身(相当于指针)不可改变。

      值类型变量,举个例子说明一下:

        public class Student
        {
            public readonly int Age;
    
            public Student(int age)
            {
                this.Age = age;
            }
        }

      Student的实例Age在构造函数中被赋值以后就不可以改变,下面的代码不会编译通过:

    Student student = new Student(20);
    student.Age = 21; //错误信息:无法对只读的字段赋值(构造函数或变量初始化器中除外)

      引用类型变量,举个例子说明一下:

        public class Student
        {
            public int Age; //注意这里的Age是没有readonly修饰符的
    
            public Student(int age)
            {
                this.Age = age;
            }
        }
    
        public class School
        {
            public readonly Student Student;
    
            public School(Student student)
            {
                this.Student = student;
            }
        }

      School实例的Student是一个引用类型的变量,赋值后,变量不能再指向其他任何的Student实例,所以,下面的代码将不会编译通过:

    School school = new School(new Student(10));
    school.Student = new Student(20);//错误信息:无法对只读的字段赋值(构造函数或变量初始化器中除外)

      引用本身不可以改变,但是引用说指向的实例的值是可以改变的。所以下面的代码是可以编译通过的:

    School school = new School(new Student(10));
    school.Student.Age = 20;

      在构造方法中,我们可以多次对Readonly修饰的常量赋值。举个例子说明一下:

        public class Student
        {
            public readonly int Age = 20;//注意:初始化器实际上是构造方法的一部分,它其实是一个语法糖
    
            public Student(int age)
            {
                this.Age = age;
                this.Age = 25;
                this.Age = 30;
            }
        }

    总结

      Const和Readonly的最大区别(除语法外)
      Const的变量是嵌入在IL代码中,编译时就加载好,不依赖外部dll(这也是为什么不能在构造方法中赋值)。Const在程序集更新时容易产生版本不一致的情况。
    Readonly的变量是在运行时加载,需请求加载dll,每次都获取最新的值。Readonly赋值引用类型以后,引用本身不可以改变,但是引用所指向的实例的值是可以改变的。在构造方法中,我们可以多次对Readonly赋值。

  • 相关阅读:
    hdu 1290 献给杭电五十周年校庆的礼物 (DP)
    hdu 3123 GCC (数学)
    hdu 1207 汉诺塔II (DP)
    hdu 1267 下沙的沙子有几粒? (DP)
    hdu 1249 三角形 (DP)
    hdu 2132 An easy problem (递推)
    hdu 2139 Calculate the formula (递推)
    hdu 1284 钱币兑换问题 (DP)
    hdu 4151 The Special Number (DP)
    hdu 1143 Tri Tiling (DP)
  • 原文地址:https://www.cnblogs.com/daidaibao/p/4214268.html
Copyright © 2011-2022 走看看