[转帖]C# const和static readonly有什么区别?
作者:作者不详  发布日期:2011/06/12 17:53:58
[转帖]C# const和static readonly有什么区别?

const和static readonly(一)

我们都知道,const和static readonly的确很像:通过类名而不是对象名进行访问,在程序中只读等等。在多数情况下可以混用。

二者本质的区别在于,const的值是在编译期间确定的,因此只能在声明时通过常量表达式指定其值。而static readonly是在运行时计算出其值的,所以还可以通过静态构造函数来赋值。

本例利用前后两个程序段的对比,来区别const和static readonly。

测试类:

using System;
using System.Collections.Generic;
using System.Text;
namespace Example02Lib
{
   public class Class1  
   {      
      public const String strConst = "Const";      
      public static readonly String strStaticReadonly = "StaticReadonly";
      
      //public const String strConst = "Const Changed";
      
      //public static readonly String strStaticReadonly = "StaticReadonly Changed"; 
   }
}

客户端代码:

using System;
using System.Collections.Generic;
using System.Text;
using Example02Lib;
namespace Example02
{
   class Program
   {
      static void Main(string[] args)
      {
         //修改Example02中Class1的strConst初始值后,只编译Example02Lib项目
         //然后到资源管理器里把新编译的Example02Lib.dll拷贝Example02.exe所在的目录,执行Example02.exe
         //切不可在IDE里直接调试运行因为这会重新编译整个解决方案!!
         //可以看到strConst的输出没有改变,而strStaticReadonly的输出已经改变
         //表明Const变量是在编译期初始化并嵌入到客户端程序,而StaticReadonly是在运行时初始化的
         Console.WriteLine("strConst : {0}", Class1.strConst);
         Console.WriteLine("strStaticReadonly : {0}", Class1.strStaticReadonly);
         Console.ReadLine();
      }
   }
}


执行结果:

strConst : Const

strStaticReadonly : StaticReadonly

修改后的示例:

测试类:

using System;
using System.Collections.Generic;
using System.Text;
namespace Example02Lib
{
   public class Class1
   {
      //public const String strConst = "Const"; 
      //public static readonly String strStaticReadonly = "StaticReadonly";
      
      public const String strConst = "Const Changed";
      public static readonly String strStaticReadonly = "StaticReadonly Changed";
   }
}



执行结果:

strConst : Const

strStaticReadonly : StaticReadonly Changed

转自百度hi



const、readonly和static(二)

在第四节中,我介绍了常量的定义,其关键字就是const。在定义常量时,必须赋予其初始值。一旦赋予了初始值后,就不能修改其值。也就是所谓的常量值不能更改的含义。由于C#是一门纯粹的面向对象语言,并不存在一个常量或者变量游离于对象之外,因此,这些定义,必然都是在一个类型内完成的。

关于常量的使用,除了会用作一些算法的临时常量值以外,最重要的是定义一些全局的常量,被其他对象直接调用。而集中这些常量最好的类型是struct(结构)。关于struct我会在后面的章节详细讲解,在这里仅举一例说明常量的这种运用。例如,我们需要在.Net下使用FTP,那么一些特定的FTP代码就可以通过这种方式完成定义,如下所示:

public struct FtpCode
{
   public const string ConnectOk = "220";
   public const string RequiredPassword = "331";
   public const string LoginOk = "230";
   public const string PasvOk = "227";
   public const string CwdOk = "250";
   public const string PwdOk = "257";
   public const string TransferOk = "226";
   public const string ListOk = "150";
   public const string PortOK = "200";
   public const string NoFile = "550";
}


要使用这些常量,可以直接调用,例如FtpCode.ConnectOk。如果结构FtpCode仅用于本程序集内部,也可以把结构类型和内部的常量设置为internal。采用这种方式有三个好处:
1、集中管理全局常量,便于调用;
2、便于修改,一旦Ftp的特定代码发生变化,仅需要修改FtpCode中的常量值即可,其余代码均不受影响;
3、便于扩展。要增加新的Ftp代码,可以直接修改结构FtpCode,其余代码不受影响。

虽然说变量的值可以修改,但我们也可以定义只读的变量,方法就是在定义的时候加上关键字readonly。如下定义:

public readonly int number = 20;


变量number的值此时是只读的,不能再对其进行重新赋值的操作。在定义只读变量的时候,建议必须为变量赋予初值。如果不赋予初值,.Net会给与警告,同时根据其类型不同,赋予不同的初值。例如int类型赋初值为0,string类型赋初值为null。由于定义的只读变量其值不可修改,因此不赋初值的只读变量定义,没有任何意义,反而容易造成空引用对象的异常。

static的意义与const和readonly迥然不同。const仅用于常量定义,readonly仅用于变量定义,而static则和常量、变量无关,它是指所定义的值与类型有关,而与对象的状态无关。

前面我已介绍,所谓“对象”,可以称为一个类型的实例,以class类型为例,当定义了一个类类型之后,要创建该类型的对象,必须进行实例化,方可以调用其属性或者方法。例如User类型的Name、Password属性,SignIn和SignOut方法,就都是与对象相关的,要调用这些属性和方法,只能通过实例化对象来调用,如下所示:

User user = new User();
user.Name = "bruce zhang";
user.Password = "password";
user.SignIn();
user.SignOut();


然而,我们在定义类的成员时,也可以利用static关键字,定义一些与对象状态无关的类成员,例如下面的代码:

public class LogManager
{
   public static void Logging(string logFile,string log)
   {
      using (StreamWriter logWriter = new StreamWriter(logFile,true))
      {
         logWriter.WriteLine(log);
      }
   }
}


方法Logging为static方法(静态方法),它们与类LogManager的对象状态是无关的,因此调用这个方法时,并不需要创建LogManager的实例:

LogManager.Logging ("log.txt","test.");

所谓“与对象状态无关”,还需要从实例化谈起。在对一个类类型进行实例化操作的时候,实际上就是在内存中分配一段空间,用以创建该对象,并储存对象的一些值,如Name和Password等。对同一个类类型,如果没有特殊的限制,是可以同时创建多个对象的,这些对象被分配到不同的内存空间中,它们的类型虽然一样,却具有不同的对象状态,如内存地址、对象名、以及对象中各个成员的值等等。例如,我们可以同时创建两个User对象:

User user1 = new User();
User user2 = new User();

由于Name和Password属性是和对象紧密相关的,方法SignIn和SignOut的实现也调用了内部的Name和Password属性值,因此也和对象紧密相关,所以这些成员就不能被定义为静态成员。试想一下,如果把Name和Password属性均设置为静态属性,则设置其值时,只能采用如下形式:

User.Name = "bruce zhang";
User.Password = "password";

显然,此时设置的Name和Password就与实例user无关,也就是说无论创建了多少个User实例,Name和Password都不属于这些实例,这显然和User类的意义相悖。对于方法SignIn和SignOut,也是同样的道理。当然我们也可以更改方法的定义,使得该方法可以被定义为static,如下所示:

public class User
{
   public static void SignIn(string userName, string password)
   {
      //代码略
   }
   public static void SignOut(string userName, string password)
   {
      //代码略
   }
}

由于SignIn和SignOut方法需要调用的Name和Password值改为从方法参数中传入,此时这两个方法就与对象的状态没有任何关系。定义好的静态方法的调用方式略有不同:

User user = new User();
user.Name = "bruce zhang";
user.Password = "password";
User.SignIn(user.Name, user.Password);
User.SignIn(user.Name, user.Password);


两相比较,这样的修改反而导致了使用的不方便。因此,当一个方法与对象的状态有较紧密的联系时,最好不要定义为静态方法。

那么为什么在LogManager类中,我将Logging方法均定义为静态方法呢?这是因为该方法与对象状态没有太大的关系,如果将方法的参数logFile和log定义为LogManager类的属性,从实际运用上也不合理,同时也会导致使用的不方便。最重要的是,一旦要调用非静态方法,不可避免的就需要创建实例对象。这会导致不必要的内存空间浪费。毕竟LogManager类型对于调用者而言,仅在于其Logging方法,而和对象的状态没有太大的关系,因此并不需要为调用这个方法专门去创建一个实例。这一点是和User类型是完全不同的。

在一个类类型的定义中,既可以允许静态成员,也可以允许非静态成员。然而在一个静态方法中,是不允许直接调用同一类型的非静态方法的,如下所示:

public class Test
{
   private void Foo1()
   {
      //代码略;
   }
   public static void Foo2()
   {
      Foo1(); //错误;
   }
   public void Foo3()
   {
      Foo1(); //正确;
   }
}


在静态方法Foo2中,直接调用了同一类型Test下的私有非静态方法Foo1,将会发生错误;而非静态方法Foo3对Foo1的调用则正确。如要在静态方法Foo2中正确调用Foo1方法,必须创建Test类的实例,通过它来调用Foo1方法,修改如下:

public static void Foo2()
{
   Test test = new Test();
   testFoo1(); //正确;
}


在Foo2方法中,创建了Test的实例,通过实例对象test来调用Foo1方法。需要注意的是虽然Foo1方法是private方法,但由于Foo2方法本身就在Test对象中,所以此时的私有方法Foo1是可以被调用的,因为对象的封装仅针对外部的调用者而言,对于类型内部,即使是private,也是可以被调用的。
 
对于类型的静态属性成员而言,具有和静态方法一样的限制。毕竟,从根本上说,类型的属性,其实就是两个get和set方法。

如果在类中定义了static的字段,有两种方式对其初始化。一是在定义时初始化字段,或者是在类型的构造器中为这些静态字段赋予初始值。例如:

class ExplicitConstructor
{
   private static string message;
   public ExplicitConstructor()
   {
      message = "Hello World";
   }
   public static string Message
   {
      get { return message; }
   }
}

class ImplicitConstructor
{
   private static string message = "Hello World";
   public static string Message
   {
      get { return message; }
   }
}


在类ExplicitConstructor中,是利用构造器为静态字段message初始化值,而在类ImplicitConstructor中,则是直接在定义时初始化message静态字段。虽然这两种方式均可达至初始化的目的,但后者在性能上有明显的优势(有兴趣者,可以阅读我博客上的一篇文章http://wayfarer.cnblogs.com/archive/2004/12/20/78817.html)。因此,我建议当需要初始化静态字段时,应直接初始化。

如果对于静态字段未设置值,.Net会给出警告,并根据类型的不同赋予不同的初始值。此外,static还可以和readonly结合起来使用,定义一个只读的静态变量。但是static不能应用到常量的定义中。

在C# 1.x中,static并不能用来修饰类类型,也就是说,我们不能定义一个静态类。然而对于一个类类型,如果其成员均为静态成员,则此时实例化该类是没有意义的。此时,我们常常将构造器设置为private,同时将其类设置为sealed(sealed表明该类不可继承,关于sealed会在后面介绍)。这样就可以避免对类的实例化操作,如前面定义的LogManager,即可以修改定义:

public sealed class LogManager
{
   private LogManager()
   {}
   public static void Logging(string logFile,string log)
   {
      using (StreamWriter logWriter = new StreamWriter(logFile,true))
      {
         logWriter.WriteLine(log);
      }
   }
}


C# 2.0支持静态类的定义,方法是在类前面加上static关键字,如:

public static class LogManager{}

由于静态类不支持实例化操作,因此在静态类的定义中,不允许再添加sealed或abstract关键字,也不允许继承某个类或被某个类继承,而类的成员中,也只能是静态成员,且不能定义构造器。由于不存在类的继承关系,因此,静态类成员中,也不允许有protected或protected internal作为访问限制修饰符。

转自:张逸:晴窗笔记



本文来源:
上一篇 下一篇