• const、readonly和static


    《叩开C#之门》系列之五

    五、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作为访问限制修饰符。

  • 相关阅读:
    java mail
    hibernate 批量处理数据
    动态规划0—1背包问题
    FreeCMS开发过程问题总结(持续更新中)
    RapeLay(电车之狼R)的结局介绍 (隐藏结局攻略)
    笔记本键盘输入错乱,字母都变成数字了
    眼下最好的JSP分页技术
    在一个字符串中找到第一个仅仅出现一次的字符
    央行力保首套房贷背后暗藏何种玄机?
    HDU2149-Good Luck in CET-4 Everybody!(博弈,打表找规律)
  • 原文地址:https://www.cnblogs.com/wayfarer/p/386658.html
Copyright © 2020-2023  润新知