[转帖]C# const和static readonly有什么区别?


[转帖]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作为访问限制修饰符。

转自:张逸:晴窗笔记

本文来源:
版权声明:本文为开发框架文库发布内容,转载请附上原文出处连接
C/S框架网
上一篇:C#调用C++编译的DLL详解
下一篇:C#常用关键字名词解释
评论列表

发表评论

评论内容
昵称:
关联文章

[]C# conststatic readonly什么区别?
C# 关键字conststatic readonly 区别
C/S框架标准版高级版什么区别
[]ACTIVE OBJECT 模式
[]C#如何调用非托管函数(一)
[]C#如何调用非托管函数(二)-传递结构
CSFramework精简版标准版什么区别
[]FreeTextBox添加自定义按钮
[]C#如何调用非托管函数(三)-实现回调函数
C#.Net String类型Null与String.Empty什么区别
[]C#如何实现DataGridView到DataGridView的拖拽
接口抽象类的区别
基础版V2.0与标准版V2.2什么区别
MiniFramework蝇量级开发框架与轻量级开发框架什么区别
Dev表格列的勾选框无效,单元格不可编辑,Editable与ReadOnly区别
精简版什么功能?
CC++的区别,cc#c++区别
开发框架使用.NET Reactor(加壳+代码混淆)与加密狗加密什么区别
C#.Net 关键字refout的区别
C#.Net值类型引用类型区别

热门标签
.NET5 .NET6 .NET7 APP Auth-软件授权注册系统 Axios B/S B/S开发框架 Bug Bug记录 C#加密解密 C#源码 C/S CHATGPT CMS系统 CodeGenerator CSFramework.DB CSFramework.EF CSFrameworkV1学习版 CSFrameworkV2标准版 CSFrameworkV3高级版 CSFrameworkV4企业版 CSFrameworkV5旗舰版 CSFrameworkV6.0 DAL数据访问层 Database datalock DbFramework Demo教学 Demo下载 DevExpress教程 DOM EF框架 Element-UI EntityFramework ERP ES6 Excel FastReport GIT HR IDatabase IIS JavaScript LINQ MES MiniFramework MIS NavBarControl Node.JS NPM OMS ORM PaaS POS Promise API Redis SAP SEO SQL SQLConnector TMS系统 Token令牌 VS2022 VSCode VUE WCF WebApi WebApi NETCore WebApi框架 WEB开发框架 Windows服务 Winform 开发框架 Winform 开发平台 WinFramework Workflow工作流 Workflow流程引擎 版本区别 报表 踩坑日记 操作手册 代码生成器 迭代开发记录 基础资料窗体 架构设计 角色权限 开发sce 开发技巧 开发教程 开发框架 开发平台 开发指南 客户案例 快速搭站系统 快速开发平台 秘钥 密钥 权限设计 软件报价 软件测试报告 软件简介 软件开发框架 软件开发平台 软件开发文档 软件体系架构 软件下载 软著证书 三层架构 设计模式 生成代码 实用小技巧 收钱音箱 数据锁 数据同步 微信小程序 未解决问题 文档下载 喜鹊ERP 喜鹊软件 系统对接 详细设计说明书 行政区域数据库 需求分析 疑难杂症 蝇量级框架 蝇量框架 用户管理 用户开发手册 用户控件 在线支付 纸箱ERP 智能语音收款机 自定义窗体 自定义组件 自动升级程序