天天看點

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

詳細讨論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作為通路限制修飾符。

原文位址:

http://www.cnblogs.com/wayfarer/archive/2006/04/27/386658.html

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