共用方式為


探索使用類別與物件的物件導向程式設計

在本教學課程中,您會建置主控台應用程式,並查看屬於 C# 語言一部分的基本物件導向功能。

必要條件

安裝指示

在 Windows 上,使用此 WinGet 組態檔 來安裝所有必要條件。 如果您已安裝某些專案,WinGet 將會略過此步驟。

  1. 下載檔案,然後按兩下以執行它。
  2. 閱讀許可協議,輸入 y,然後在系統提示接受時選取 [輸入]。
  3. 如果您在任務欄中收到閃爍的用戶帳戶控制 (UAC) 提示,請允許安裝繼續。

在其他平臺上,您必須個別安裝這些元件。

  1. .NET SDK 下載頁面下載建議的安裝程式,然後按兩下以執行它。 下載頁面會偵測您的平臺,並建議您平臺的最新安裝程式。
  2. Visual Studio Code 首頁下載最新的安裝程式,然後按兩下以執行它。 該頁面還會偵測您的平臺,並且應該提供適合您系統的正確連結。
  3. 按兩下 C# DevKit 擴充功能頁面上的 [安裝] 按鈕。 這樣會開啟 Visual Studio 程式代碼,並詢問您是否要安裝或啟用延伸模組。 選取 [安裝]。

建立您的應用程式

使用終端機視窗,建立名為 Classes 的目錄。 您可以在那裡構建您的應用程序。 在主控台視窗中變更至該目錄並輸入 dotnet new console。 這個命令會建立您的應用程式。 開啟 Program.cs。 其看起來應該如下:

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

在此教學課程中,您將創建代表銀行帳戶的新型別。 開發人員通常會在不同的文字檔中定義每個類別。 隨著程式大小增加,這麼做會使它更易於管理。 在 Classes 目錄中,建立名為 BankAccount.cs 的新檔案。

此檔案包含 銀行帳戶的定義。 物件導向程式設計會以類別的形式建立類型來組織程式碼。 這些類別包含代表特定實體的程式碼。 BankAccount 類別代表銀行帳戶。 程式碼會透過方法和屬性來實作特定的作業。 在此教學課程中,銀行帳戶支援此行為:

  1. 它具有能唯一識別銀行帳戶的 10 位數數字。
  2. 它具有能儲存擁有者一個或多個名稱的字串。
  3. 餘額可以恢復。
  4. 它接受存款。
  5. 它接受提款。
  6. 初始餘額必須為正數。
  7. 提款不可使餘額成為負數。

定義銀行帳戶類型

您可以從建立能定義該行為之類別的基礎項目來開始。 使用 File:New 命令建立新的檔案。 命名為 BankAccount.cs。 將下列程式碼新增至 BankAccount.cs 檔案:

namespace Classes;

public class BankAccount
{
    public string Number { get; }
    public string Owner { get; set; }
    public decimal Balance { get; }

    public void MakeDeposit(decimal amount, DateTime date, string note)
    {
    }

    public void MakeWithdrawal(decimal amount, DateTime date, string note)
    {
    }
}

在繼續之前,讓我們先看看你構建了什麼。 namespace 宣告能提供以邏輯方式組織程式碼的方式。 本教學課程相對較小,因此您可以將所有程式碼放在一個命名空間中。

public class BankAccount 能定義您要建立的類別 (或類型)。 類別宣告後面的 {} 之內的所有內容,皆定義該類別的狀態和行為。 有五個成員。 前三個為屬性。 屬性是資料元素,且可以具有強制執行驗證或其他規則的程式碼。 後兩個為方法。 方法是執行單一函式的程式碼區塊。 閱讀每個成員的名稱,應該能提供足夠的資訊,以供您或其他開發人員了解該類別的功能。

開啟新帳戶

第一個要實作的功能是開啟一個銀行帳戶。 當客戶開啟帳戶時,他們必須提供初始餘額,以及該帳戶的一或多個擁有者的相關資訊。

建立類型的 BankAccount 新物件需要定義指派這些值的 建構函式 建構函式是具有和該類別相同名稱的成員。 用來初始化該類別類型的物件。 將下列建構函式新增到 BankAccount 類型。 在 MakeDeposit 宣告之上放置下列程式碼:

public BankAccount(string name, decimal initialBalance)
{
    this.Owner = name;
    this.Balance = initialBalance;
}

上述程式碼會藉由包含 this 限定詞來識別要建構之物件的屬性。 該限定詞通常是選擇性的,並省略。 你也可以寫:

public BankAccount(string name, decimal initialBalance)
{
    Owner = name;
    Balance = initialBalance;
}

只有在區域變數或參數的名稱與該欄位或屬性相同時,才需要 this 限定詞。 除非必要,否則本文其餘部分會省略 this 限定詞。

當您使用 new 建立物件時,系統便會呼叫建構函式。 以下列行取代 Console.WriteLine("Hello World!"); 中的 程式碼 (以您的名字取代 <name>):

using Classes;

var account = new BankAccount("<name>", 1000);
Console.WriteLine($"Account {account.Number} was created for {account.Owner} with {account.Balance} initial balance.");

讓我們來運行您迄今為止構建的內容。 如果您使用 Visual Studio,請從 [偵錯] 功能表選取 [開始但不偵錯]。 如果您使用命令列,請在建立專案的目錄中輸入 dotnet run

您是否注意到帳戶號碼是空白的? 讓我們來修正此情況。 帳戶號碼應該要在物件建構時指派。 但不應該由呼叫方負責創建它。 BankAccount 類別程式碼應該要知道如何指派新的帳戶號碼。 最簡單的方法是從 10 位數的數字開始。 建立每個新帳戶時就增加它。 最後,在物件完成建構時儲存目前的帳戶號碼。

請將成員宣告加入 BankAccount 類別。 將下列程式碼放在 { 類別開頭的左大括弧 BankAccount 後面:

private static int s_accountNumberSeed = 1234567890;

accountNumberSeed 是一個資料成員。 它是 private,這表示它只能由 BankAccount 類別中的程式碼存取。 這是將公開責任 (例如具有帳戶號碼) 和私用實作 (帳戶號碼產生的方式) 區隔開來的方法。 它也是 static,這意味著所有 BankAccount 物件都共用此變數的相同單一實例。 非靜態變數的值對於每個 BankAccount 物件的執行個體而言都是唯一的。 accountNumberSeedprivate static 欄位,因此根據 C# 命名慣例,其具有 s_ 前置詞。 s 表示 static,而 _ 表示 private 欄位。 若要初始化每個帳號的編號,請將下列兩行新增至建構函式。 將它們放在顯示 this.Balance = initialBalance 的行後面:

Number = s_accountNumberSeed.ToString();
s_accountNumberSeed++;

輸入 dotnet run 來查看結果。

進行存款和提款

您的銀行帳戶必須能接受存款及提款,才算能正常運作。 讓我們透過建立帳戶每一筆交易的日誌,來實作存款和提款。 相較於單純地在每次交易時更新餘額,追蹤每一項交易能提供數個好處。 該記錄可用來對所有交易進行稽核,以及管理每日餘額。 在必要時計算所有交易記錄的餘額,確保在任何單一交易中已修正的錯誤都會正確地在下一次計算中反映出來。

首先建立代表交易的新類型。 交易是record類型,沒有任何責任。 它需要幾個屬性。 建立名為 Transaction.cs 的新檔案。 將下列程式碼加入該檔案:

namespace Classes;

public record Transaction(decimal Amount, DateTime Date, string Notes);

現在,讓我們將 List<T>Transaction 物件新增到 BankAccount 類別中。 在 BankAccount.cs 檔案中的建構函式後面新增下列宣告:

private List<Transaction> _allTransactions = new List<Transaction>();

現在,讓我們正確地計算 Balance。 目前的餘額可以透過針對所有交易的值進行加總來取得。 由於目前的代碼,您只能獲取帳戶的初始餘額,因此您必須更新 Balance 屬性。 以下列程式碼取代 public decimal Balance { get; } 中的行

public decimal Balance
{
    get
    {
        decimal balance = 0;
        foreach (var item in _allTransactions)
        {
            balance += item.Amount;
        }

        return balance;
    }
}

此範例顯示出屬性的一個重要層面。 現在您會在另一個程式要求餘額時計算該值。 您的計算會列舉所有交易,並提供總和作為目前的餘額。

接下來,請實作 MakeDepositMakeWithdrawal 方法。 這些方法執行最後兩條規則:初始餘額必須為正數,任何提款都不得產生負餘額。

這些規則引進 例外狀況 的概念。 通常的做法是指出方法若無法完成其工作時拋出例外。 例外狀況的類型和與它相關的訊息會描述該錯誤。 在這裡,如果存款的金額是不大於 0,MakeDeposit 方法便會擲回例外狀況。 如果提款金額不大於 0,或是套用提款後會導致負數餘額,則 MakeWithdrawal 方法會擲回例外狀況。 將下列程式碼新增至 _allTransactions 清單宣告後面:

public void MakeDeposit(decimal amount, DateTime date, string note)
{
    if (amount <= 0)
    {
        throw new ArgumentOutOfRangeException(nameof(amount), "Amount of deposit must be positive");
    }
    var deposit = new Transaction(amount, date, note);
    _allTransactions.Add(deposit);
}

public void MakeWithdrawal(decimal amount, DateTime date, string note)
{
    if (amount <= 0)
    {
        throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
    }
    if (Balance - amount < 0)
    {
        throw new InvalidOperationException("Not sufficient funds for this withdrawal");
    }
    var withdrawal = new Transaction(-amount, date, note);
    _allTransactions.Add(withdrawal);
}

throw 陳述式會引發 例外狀況。 目前區塊執行結束,轉移控制權至呼叫堆疊中找到的第一個相符 catch 區塊。 您稍後新增一個 catch 區塊來測試此程式碼。

建構函式應該進行一項變更,以便新增初始交易,而不是直接更新餘額。 由於您已撰寫 MakeDeposit 方法,請從建構函式呼叫它。 完成的建構函式應該看起來如下:

public BankAccount(string name, decimal initialBalance)
{
    Number = s_accountNumberSeed.ToString();
    s_accountNumberSeed++;

    Owner = name;
    MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
}

DateTime.Now 是會傳回目前日期和時間的屬性。 在 Main 方法中新增一些存金和退金來測試此程式碼,並遵循建立新 BankAccount 的程式碼:

account.MakeWithdrawal(500, DateTime.Now, "Rent payment");
Console.WriteLine(account.Balance);
account.MakeDeposit(100, DateTime.Now, "Friend paid me back");
Console.WriteLine(account.Balance);

接下來,透過建立具有負數餘額的帳戶,來測試是否能攔截到錯誤情況。 在您剛才新增的上述程式碼之後新增下列程式碼:

// Test that the initial balances must be positive.
BankAccount invalidAccount;
try
{
    invalidAccount = new BankAccount("invalid", -55);
}
catch (ArgumentOutOfRangeException e)
{
    Console.WriteLine("Exception caught creating account with negative balance");
    Console.WriteLine(e.ToString());
    return;
}

您可以使用語句來標示可能擲回例外狀況的程式碼區塊,並捕捉您預期的例外狀況。 您可以使用同樣的技巧來測試會針對負數餘額擲回例外狀況的程式碼。 在你的 invalidAccount 方法中,在 Main 宣告之前新增下列程式碼:

// Test for a negative balance.
try
{
    account.MakeWithdrawal(750, DateTime.Now, "Attempt to overdraw");
}
catch (InvalidOperationException e)
{
    Console.WriteLine("Exception caught trying to overdraw");
    Console.WriteLine(e.ToString());
}

儲存檔案並輸入 dotnet run 來嘗試它。

挑戰 - 記錄所有交易

若要完成此教學課程,您可以撰寫會為交易記錄創建 GetAccountHistorystring 方法。 將此方法新增到 BankAccount 型別:

public string GetAccountHistory()
{
    var report = new System.Text.StringBuilder();

    decimal balance = 0;
    report.AppendLine("Date\t\tAmount\tBalance\tNote");
    foreach (var item in _allTransactions)
    {
        balance += item.Amount;
        report.AppendLine($"{item.Date.ToShortDateString()}\t{item.Amount}\t{balance}\t{item.Notes}");
    }

    return report.ToString();
}

此歷程記錄使用 StringBuilder 類別來設定針對每個交易包含單一行之字串的格式。 您在這些教學之前使用了字串格式。 一個新的字元是 \t。 它會插入 Tab 鍵來設定輸出的格式。

新增下列行以在 Program.cs 中測試它:

Console.WriteLine(account.GetAccountHistory());

運行您的程序並檢查結果。

下一步

如果遇到問題,您可以在我們的 GitHub 存放庫 \(英文\) 中查看此教學課程的原始程式碼。

您可以繼續進行物件導向程式設計教學課程。

您可以在下列文章深入了解這些概念: