常量的定義,其關鍵字就是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: }
1: public static class LogManager{}