天天看點

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屬性均設定為靜态屬性,則設定其值時,隻能采用如下形式:

顯然,此時設定的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.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方法,修改如下:

  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

   } 

在類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()

 {}

c# 2.0支援靜态類的定義,方法是在類前面加上static關鍵字,如:

public static class logmanager{}

由于靜态類不支援執行個體化操作,是以在靜态類的定義中,不允許再添加sealed或abstract關鍵字,也不允許繼承某個類或被某個類繼承,而類的成員中,也隻能是靜态成員,且不能定義構造器。由于不存在類的繼承關系,是以,靜态類成員中,也不允許有protected或protected internal作為通路限制修飾符。

繼續閱讀