詳細讨論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
