[轉]再談C#中的Const、ReadOnly和Static變量


常量的定義,其關鍵字就是const。在定義常量時,必須賦予其初始值。一旦賦予了初始值后,就不能修改其值。也就是所謂的常量值不能更改的含義。由於C#是一門純粹的面向對象語言,並不存在一個常量或者變量游離於對象之外,因此,這些定義,必然都是在一個類型內完成的。

關於常量的使用,除了會用作一些算法的臨時常量值以外,最重要的是定義一些全局的常量,被其他對象直接調用。而集中這些常量最好的類型是struct(結構)。關於struct我會在后面的章節詳細講解,在這里僅舉一例說明常量的這種運用。例如,我們需要在.Net下使用FTP,那么一些特定的FTP代碼就可以通過這種方式完成定義,如下所示:

   1: public struct FtpCode
   2: {
   3:  public const string ConnectOk = "220";
   4:  public const string RequiredPassword = "331";
   5:  public const string LoginOk = "230";
   6:  public const string PasvOk = "227";
   7:  public const string CwdOk = "250";
   8:  public const string PwdOk = "257";
   9:  public const string TransferOk = "226";
  10:  public const string ListOk = "150";
  11:  public const string PortOK = "200";
  12:  public const string NoFile = "550";
  13: }

要使用這些常量,可以直接調用,例如FtpCode.ConnectOk。如果結構FtpCode僅用於本程序集內部,也可以把結構類型和內部的常量設置為internal。采用這種方式有三個好處:
1、集中管理全局常量,便於調用;
2、便於修改,一旦Ftp的特定代碼發生變化,僅需要修改FtpCode中的常量值即可,其余代碼均不受影響;
3、便於擴展。要增加新的Ftp代碼,可以直接修改結構FtpCode,其余代碼不受影響。

 

雖然說變量的值可以修改,但我們也可以定義只讀的變量,方法就是在定義的時候加上關鍵字readonly。如下定義:

   1: public readonly int number = 20;

變量number的值此時是只讀的,不能再對其進行重新賦值的操作。在定義只讀變量的時候,建議必須為變量賦予初值。如果不賦予初值,.Net會給與警告,同時根據其類型不同,賦予不同的初值。例如int類型賦初值為0,string類型賦初值為null。由於定義的只讀變量其值不可修改,因此不賦初值的只讀變量定義,沒有任何意義,反而容易造成空引用對象的異常。

static的意義與const和readonly迥然不同。const僅用於常量定義,readonly僅用於變量定義,而static則和常量、變量無關,它是指所定義的值與類型有關,而與對象的狀態無關。

前面我已介紹,所謂“對象”,可以稱為一個類型的實例,以class類型為例,當定義了一個類類型之后,要創建該類型的對象,必須進行實例化,方可以調用其屬性或者方法。例如User類型的Name、Password屬性,SignIn和SignOut方法,就都是與對象相關的,要調用這些屬性和方法,只能通過實例化對象來調用,如下所示:

   1: User user = new User();
   2: user.Name = "bruce zhang";
   3: user.Password = "password";
   4: user.SignIn();
   5: user.SignOut();

然而,我們在定義類的成員時,也可以利用static關鍵字,定義一些與對象狀態無關的類成員,例如下面的代碼:

   1: public class LogManager
   2: {
   3:  public static void Logging(string logFile,string log)
   4:  {
   5:   using (StreamWriter logWriter = new StreamWriter(logFile,true))
   6:   {
   7:    logWriter.WriteLine(log);
   8:   }
   9:  }
  10: }

方法Logging為static方法(靜態方法),它們與類LogManager的對象狀態是無關的,因此調用這個方法時,並不需要創建LogManager的實例:

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

所謂“與對象狀態無關”,還需要從實例化談起。在對一個類類型進行實例化操作的時候,實際上就是在內存中分配一段空間,用以創建該對象,並儲存對象的一些值,如Name和Password等。對同一個類類型,如果沒有特殊的限制,是可以同時創建多個對象的,這些對象被分配到不同的內存空間中,它們的類型雖然一樣,卻具有不同的對象狀態,如內存地址、對象名、以及對象中各個成員的值等等。例如,我們可以同時創建兩個User對象:

   1: User user1 = new User();
   2: User user2 = new User();

由於Name和Password屬性是和對象緊密相關的,方法SignIn和SignOut的實現也調用了內部的Name和Password屬性值,因此也和對象緊密相關,所以這些成員就不能被定義為靜態成員。試想一下,如果把Name和Password屬性均設置為靜態屬性,則設置其值時,只能采用如下形式:

   1: User.Name = "bruce zhang";
   2: User.Password = "password";

顯然,此時設置的Name和Password就與實例user無關,也就是說無論創建了多少個User實例,Name和Password都不屬於這些實例,這顯然和User類的意義相悖。對於方法SignIn和SignOut,也是同樣的道理。當然我們也可以更改方法的定義,使得該方法可以被定義為static,如下所示:

   1: public class User
   2: {
   3:  public static void SignIn(string userName, string password)
   4:  {
   5:   //代碼略
   6: }
   7:  public static void SignOut(string userName, string password)
   8:  {
   9:   //代碼略
  10: }
  11: }

由於SignIn和SignOut方法需要調用的Name和Password值改為從方法參數中傳入,此時這兩個方法就與對象的狀態沒有任何關系。定義好的靜態方法的調用方式略有不同:

   1: User user = new User();
   2: user.Name = "bruce zhang";
   3: user.Password = "password";
   4: User.SignIn(user.Name, user.Password);
   5: User.SignIn(user.Name, user.Password);

兩相比較,這樣的修改反而導致了使用的不方便。因此,當一個方法與對象的狀態有較緊密的聯系時,最好不要定義為靜態方法。

那么為什么在LogManager類中,我將Logging方法均定義為靜態方法呢?這是因為該方法與對象狀態沒有太大的關系,如果將方法的參數logFile和log定義為LogManager類的屬性,從實際運用上也不合理,同時也會導致使用的不方便。最重要的是,一旦要調用非靜態方法,不可避免的就需要創建實例對象。這會導致不必要的內存空間浪費。畢竟LogManager類型對於調用者而言,僅在於其Logging方法,而和對象的狀態沒有太大的關系,因此並不需要為調用這個方法專門去創建一個實例。這一點是和User類型是完全不同的。

在一個類類型的定義中,既可以允許靜態成員,也可以允許非靜態成員。然而在一個靜態方法中,是不允許直接調用同一類型的非靜態方法的,如下所示:

   1: public class Test
   2: {
   3:  private void Foo1()
   4:  {
   5:   //代碼略;
   6:  }
   7:  public static void Foo2()
   8:  {
   9:   Foo1();  //錯誤;
  10:  }
  11:  public void Foo3()
  12:  {
  13:   Foo1();  //正確;
  14:  }
  15: }

在靜態方法Foo2中,直接調用了同一類型Test下的私有非靜態方法Foo1,將會發生錯誤;而非靜態方法Foo3對Foo1的調用則正確。如要在靜態方法Foo2中正確調用Foo1方法,必須創建Test類的實例,通過它來調用Foo1方法,修改如下:

   1: public static void Foo2()
   2:  {
   3:   Test test = new Test();
   4:   testFoo1();  //正確;
   5:  }

在Foo2方法中,創建了Test的實例,通過實例對象test來調用Foo1方法。需要注意的是雖然Foo1方法是private方法,但由於Foo2方法本身就在Test對象中,所以此時的私有方法Foo1是可以被調用的,因為對象的封裝僅針對外部的調用者而言,對於類型內部,即使是private,也是可以被調用的。
對於類型的靜態屬性成員而言,具有和靜態方法一樣的限制。畢竟,從根本上說,類型的屬性,其實就是兩個get和set方法。

如果在類中定義了static的字段,有兩種方式對其初始化。一是在定義時初始化字段,或者是在類型的構造器中為這些靜態字段賦予初始值。例如:

   1: class ExplicitConstructor
   2: {
   3: private static string message;
   4: public ExplicitConstructor()
   5:    {
   6: message = "Hello World";
   7:    }
   8:    public static string Message
   9:    {
  10:      get { return message; }
  11:    }  
  12: }
  13: class ImplicitConstructor
  14: {
  15: private static string message = "Hello World"; 
  16: public static string Message
  17:    {
  18:      get { return message; }
  19:    } 
  20: }

在類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,即可以修改定義:

   1: public sealed class LogManager
   2: {
   3:  private LogManager()
   4:  {}
   5:  public static void Logging(string logFile,string log)
   6:  {
   7:   using (StreamWriter logWriter = new StreamWriter(logFile,true))
   8:   {
   9:    logWriter.WriteLine(log);
  10:   }
  11:  }
  12: }
 
C# 2.0支持靜態類的定義,方法是在類前面加上static關鍵字,如:
   1: public static class LogManager{}
由於靜態類不支持實例化操作,因此在靜態類的定義中,不允許再添加sealed或abstract關鍵字,也不允許繼承某個類或被某個類繼承,而類的成員中,也只能是靜態成員,且不能定義構造器。由於不存在類的繼承關系,因此,靜態類成員中,也不允許有protected或protected internal作為訪問限制修飾符。
 
 
原文地址:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM