定義C#的類

來源:互聯網
上載者:User

《叩開C#之門》系列之四

四、定義C#的類

既然類類型是C#中最重要、最常見的類型,因此它是我要介紹的重點,實際上,C#中的許多特性都可以通過類類型來體現。

前面已經介紹,一個類對象中,主要包括欄位、屬性和方法。不過除此之外,在類類型中還可以定義嵌套類,也可以定義一個常量。

對於一個沒有任何編程知識的初學者而言,也許還應介紹一下常量與變數。不過從它們的名字就可以非常清晰地辨明二者的區別,常量其值是不可改變的,而變數的值則可以修改,除非該變數是唯讀(如設定為readonly)。

最好的常量的例子就是圓周率值,這個值當然是不變化的,如果保留小數點後七位,其值為3.1415926。然而如果我們要頻繁使用該值,則輸入這麼多數字自然不是好的選擇,何況一旦使用者要求圓周率更加精確,需要保留更多的小數位,要修改起來就非常困難了。因此我們需要定義一個常量:
const PI = 3.1415926;

此時pi就代表了3.1415926,要使用圓周率,直接取pi的值即可:
square = PI * radius;

注意上面的運算式,其中PI是常量,在定義它時,使用了const關鍵字;而square和radius則為變數,定義如下:
double square, radius;

一旦定義了PI為常量,那麼該類對象被建立之後,就不能修改了,而變數是可以修改的。如下的代碼是錯誤的:
PI = 3.1415926535897;
square = PI * radius;

而下面的兩行代碼則是正確的:
radius = 2.5;
square = PI * radius;

類的欄位其實也是變數。如系列三中的類User,就包含有欄位m_name,m_password,m_tryCounter。它們的類型分別為string,string,int。欄位仍然可以利用public,internal,protected,private來修飾它。不過,我建議如非特殊情況,不要將欄位修飾為public。因為,根據”對象封裝”的原則,應盡量避免將一個類型的欄位以公有方式提供給外部。畢竟,對於欄位而言,對象對它的控制非常弱,一旦公開在外,則調用者可以比較容易的對其進行操作,尤其是寫操作,從而可能會導致錯誤。例如,我們為前面定義的User類增加一個age(年齡)欄位,假如我將其定義為public欄位,如下所示:
public int Age;

那麼調用者可能會將Age的值設為負數:
user.Age = -5;

對於欄位的定義而言,並不能判斷這樣一種不合常理的操作,因為我們對欄位的寫操作的控制無能為力。

大家可以看到,這裡所謂的欄位值,其實可以對應於前面所講的對象的屬性。例如姓名,年齡,就是一個使用者的屬性。如果欄位不能設定為public,那麼調用者又如何訪問它們呢?答案就是使用C#類中的property(屬性)。

所謂“屬性”,很大程度可以看作是對“欄位”的一種封裝,它利用一種被稱為“get/set訪問器”分別控制對欄位的讀寫操作,並暴露一個屬性值,如Age屬性:
private int m_age;
public int Age
{
 get {return m_age;}
 set
 {
  if (value < 0)
  {
   throw new ArgumentOutOfRangeException("Age must be greater than or equal to 0");
  }
  m_age = value;
 }
}

上面的代碼中,throw語句的作用是拋出一個異常,我們暫時可以不去理會它,而是將注意力放到get和set訪問器上。首先,我們定義了一個私人欄位m_age,然後再定義一個公用屬性Age。在該屬性中,get返回私人欄位的值m_age,而在set中,首先會判斷value的值,如果小於0,那麼這個值是非法的,就將拋出一個異常,停止往下執行,並告訴你對Age值的設定錯誤了。當然,我們也可以為value值設定更嚴格的要求,例如不允許value大於150。至少人的年齡現在沒有超過150歲的吧。

也許會有人疑問value究竟是什嗎?它其實是C#中提供的關鍵字,代表的就是你賦給該屬性的真正的值,例如:
user.Age = 30;

此時是對Age屬性賦值,.Net會執行set訪問器,而value值就是30。然後判斷30是否小於0。顯然不符合條件,不會拋出異常,繼續執行,將value值30賦給欄位m_age。為什麼要賦給m_age呢?讓我們再看看get訪問器,它其實就是一個讀操作,返回的值是什嗎?對了,就是欄位m_age,如下所示:
user.Age = 30;     //set操作,將欄位m_age設定為30;
Console.WriteLine(“User’s Age is {0}.”, user.Age);   //get操作,將m_age的值取出;

此時就會在控制台下顯示:
User’s Age is 30.

此外,對於一些特殊的要求,我們在將欄位封裝為屬性時,可以只設定它的get訪問器或者set訪問器,這樣這個屬性就是唯讀屬性,或者唯寫屬性了。這樣顯然更有利於對象的封裝。畢竟對於公用欄位而言,我們最能可以控制它為唯讀(設定為readonly),卻無法設定為唯寫。

從上可以看到,實際上屬性就是對欄位進行一次封裝。通過這個封裝,使我們對欄位m_age的讀寫都具有了控制功能,至少現在的Age屬效能夠控制賦值為負數的情況了。這就是屬性的好處。

在C# 2.0中,除了可以對整個屬性設定public等存取修飾詞外,對內部的get/set訪問器同樣可以設定存取修飾詞,當然它要受到一定的限制。由於有些限制和介面、重寫有關,我暫時不會介紹,在這裡,我僅介紹訪問器和屬性的存取修飾詞衝突問題。
1、如果整個屬性被設定為public,則其訪問器沒有限制;
2、如果整個屬性被設定為protected internal,則訪問器的訪問修飾僅能設定為internal,protected或者private中的一種;
3、如果整個屬性被設定為internal或者protected,那麼訪問器的訪問修飾只能是private。
如下例:
public Class A
{
 private string m_text;
 private int m_count;
 public string Text
 {
  get {return m_text;}
  protected set { m_text = value;}
 }
 internal int Count
 {
  private get {return 5;}
  private set {m_count = value}
}
}

從程式的實質來看,其實屬性就是一種特殊的方法,它等同於下面的代碼:
public int GetAge()
{
 return m_age;
}
public void SetAge(int age)
{
 m_age = age;
}

從這個意義上來理解get/set訪問器的存取層級修飾,就更容易理解了。實質上,所謂的訪問器的存取層級修飾,不外乎就是對方法進行存取層級修飾罷了。當然,C#中提供的屬性要比訪問欄位的get/set方法更加簡便。一般而言,如要定義方法,應該是和一個對象的行為有關,例如系列三定義的User類中的SignIn()和SignOut()方法,它們代表的是對象User的行為:登入和退出。

定義一個類的方法,必須包括五個要素:方法修飾符,方法名,傳回型別,參數,以及方法體,例如Add方法:
public int Add(int x, int y)
{
 return x + y;
}

public即為我們的方法修飾符,它代表了該方法能被訪問的層級。當然,修飾的方法的關鍵字還包括static,virtual,abstract等,不過這些內容會在以後介紹。方法名自然是Add了,自然屬於方法的名字。傳回型別為int,代表該方法會返回一個結果,該結果類型為int類型。參數有兩個,分別為x和y,它們的類型都是int。調用者可以通過參數傳遞值到方法體中,並對它們進行操作。方法體則是花括弧中的內容。

假設Add方法是定義在類Calculator中,那麼該方法的調用為:
Calculator cal = new Calculator();
int result = cal.Add(3,5);

通過對Add的調用,並傳入3和5的參數,最後得到結果8,並返回。因此,此時變數result的值就為8。而第一行代碼,則是利用new關鍵字對Calculator類進行執行個體化,獲得一個對象cal。通過對象cal,才可以調用Calculator類的公用方法、屬性或欄位。

為什麼要進行執行個體化呢?我們定義一個類類型,是為調用者所使用的,否則就失去其意義了。但我們定義的這樣一個類類型,僅僅是代表了某種格式而已,例如User類說明它是一個class,它擁有了一些欄位、屬性和方法。通過這樣的定義,我們在使用這些類型的對象時,.Net能夠識別它。而如果真正要調用這些類型對象,就必須進行”執行個體化”,這個操作就會在運行期間,建立一個個對象,並被放在記憶體空間中供程式調用。就好比”人”就是一個類類型,而某一個具體的人,才是被執行個體化的、真正存在的對象。要使得一個類類型被執行個體化,就需要為該類型提供”構造器”。構造器是一種特殊的方法,它沒有傳回型別,且其方法名和類型名保持一致,如Calculator類的定義以及它的構造器:
public class Calculator
{
 public Calculator()
 {
 }
 public int Add(int x, int y)
 {
  return x + y;
 }
}

Calculator()方法就是一個”構造器”,這個構造器並沒有參數,在C#中,也被稱為預設的構造器,即使不定義該構造器,.Net也會為它預設建立。例如在Calculator類中,我們完全可以刪去Calculator()構造器的定義。然而,一旦我們定義了有參數的構造器時,則該預設構造器將不存在,如果我們再需要不帶參數建立執行個體的話,就需要顯式建立該構造器了。例如之前的User類。如果姓名和密碼是該類一個非常重要的屬性,大部分情況下,如果要建立User對象時,都需要這兩個屬性的值時,我們就可以為User類專門建立一個構造器:
public class User
{
 public User(string name, string password)
 {
  m_name = name;
  m_password = password;
 } 
}

注意在這個構造器中,接收兩個參數name和password,並將其值賦給User類的欄位m_name,m_password。因此,當我們通過如下的方式建立User類的執行個體時,我們建立的對象就已經具有Name和Password的值了:
User specUser = new User("bruce zhang", "password");

然而此時如果利用下面的方式建立User的執行個體,就會出現錯誤:
User user = new User();
因為此時User類的預設構造器(即無參的構造器)已經不存在,如要支援上面的執行個體化方式,就需要在User類的定義中添加無參構造器。

是否需要為一個類定義有參的構造器,應根據具體的需要而定。以User類而言,由於該類的Name和Password屬性是一個對象必備的,那麼建立這樣一個構造器是有必要的。因為如果不具備這樣的構造器,那麼如前構造的specUser就需要用下面三行代碼來完成:
User specUser = new User();
specUser.Name = "bruce zhang";
specUser.Password = "password";

注意,在一個類的定義中,我們可以使用this關鍵字來代表其類對象本身,通過this,可以訪問到這個類的所有常量、欄位、屬性和方法,不管它們是public還是private,或者其他存取層級。雖然這個this指代的是類對象本身,也就是說它代表的就是執行個體化所產生的對象,但this的含義也僅僅限於對象的內部,從對象封裝的思想來看,它是被封裝了的,在對象外部是無法看到的。

例如下面的定義:
public class Visitor
{
 public void Visit(Element e)
 {
  Console.WriteLine("I was visited.");
 }
}
public class Element
{
 public void Accept(Visitor v)
 {
  v.Visit(this);
 }
}

在Element類中,Accept方法則傳入一個Visitor類型的參數值,在該方法中,調用參數v的方法Visit,而Visit方法傳入的是Element類型的值,由於Accept方法本身就屬於Element類,因此,我們可以把其自身傳遞到Visit方法中,也就是代碼中的this。

分析如下的程式碼片段:
Visitor v = new Visitor();
Element e = new Element();
e.Accept(v);

Element的執行個體e,執行了Accept()方法,該方法傳入的參數是Visitor類的執行個體v。那麼執行Accept方法,實質就是在其方法內部執行v.Visit()方法,並通過this關鍵字將對象e本身傳入,所以最後的結果是,列印如下的字串:
I was visited。

這這裡順便提一下命名的要求。所謂命名規範,在作為團隊開發的時候,是非常重要的。以本文為例,如何定義類名、欄位名、屬性名稱和方法名,都是有講究的。通常來說,類名、屬性名稱和方法名都要求所有單詞的首字母大寫。如果是欄位,那麼除非是公用欄位,一般而言,應將第一個單詞的首字母小寫。不過這也是變數命名的要求。由於在一個類中,可能會臨時用到一些變數,而不是欄位,為了區別一般變數和欄位,C++的程式員喜歡在變數名前加上“_”符號,許多C#程式員也沿用了這個習慣。不過我更喜歡為這些欄位名前加上“m_”。命名一定要統一,尤其是在一個團隊中,不過類似於這些臨時變數,或者非公有變數,對名字的限制要少一些,畢竟這些變數不會被類的調用者使用。此外,對於常量而言,最好定義為全部大寫的名字,如前面的定義的常量PI。

C#專門有一套完整的命名規範,有興趣的可以自己去查閱一下專門的資料。此外,不同的公司可能還有一些特定的命名規範,在這裡就不再贅述了。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.