このチュートリアルでは、コンソール アプリケーションをビルドし、C# 言語の一部である基本的なオブジェクト指向機能を確認します。
前提条件
- 最新の .NET SDK
- Visual Studio Code エディター
- C# DevKit
インストール手順
Windows では、この WinGet 構成ファイル を使用して、すべての前提条件をインストールします。 既に何かがインストールされている場合、WinGet はその手順をスキップします。
- ファイルをダウンロードし、ダブルクリックして実行します。
- 使用許諾契約書を読み、y と入力し、同意を求められたら Enter キーを選択します。
- タスク バーで点滅するユーザー アカウント制御 (UAC) プロンプトが表示された場合は、インストールを続行します。
他のプラットフォームでは、これらの各コンポーネントを個別にインストールする必要があります。
- .NET SDK のダウンロード ページから推奨インストーラーをダウンロードし、ダブルクリックして実行します。 ダウンロード ページでプラットフォームが検出され、プラットフォームの最新のインストーラーが推奨されます。
- Visual Studio Code のホーム ページから最新のインストーラーをダウンロードし、ダブルクリックして実行します。 このページではプラットフォームも検出され、リンクはシステムに適している必要があります。
- C# DevKit 拡張機能ページの [インストール] ボタンをクリックします。 これで Visual Studio Code が開き、拡張機能をインストールするか有効にするかを確認するメッセージが表示されます。 [インストール] を選択します。
アプリケーションを作成する
ターミナル ウィンドウで、「Classes」という名前のディレクトリを作成します。 そこでアプリケーションをビルドします。 このディレクトリに移動し、コンソール ウィンドウで「dotnet new console」と入力します。 このコマンドにより、アプリケーションが作成されます。
Program.cs を開きます。 内容は次のようになります。
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
このチュートリアルでは、銀行口座を表す新しい型を作成します。 通常、開発者は各クラスを別々のテキスト ファイルで定義します。 この方法なら、プログラムのサイズが大きくなっても管理が容易です。 classes ディレクトリに「BankAccount.cs」という名前の新しいファイルを作成します。
このファイルには、 銀行口座の定義が含まれています。 オブジェクト指向プログラミングを使用して "クラス" の形式で型を作成することにより、コードを整理します。 これらのクラスには、特定のエンティティを表すコードが含まれています。
BankAccount クラスは銀行口座を表します。 コードでは、メソッドとプロパティを使用した特定の操作を実装します。 このチュートリアルでは、銀行口座は次の動作をサポートします。
- 銀行口座を一意に特定する 10 桁の数字をサポートしています。
- 口座の名前、または所有者の名前を格納する文字列をサポートしています。
- 残高を取得できます。
- 預金を許可します。
- 引き出しを許可します。
- 初期残高は正の値である必要があります。
- 引き出しによって残高が負の値になることはありません。
銀行口座の型を定義する
動作を定義するクラスの基本を作成することから開始できます。 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 宣言は、コードを論理的に整理する方法を提供します。 このチュートリアルは比較的小さいため、すべてのコードを 1 つの名前空間に配置します。
public class BankAccount は、これから作成するクラスまたは型を定義します。 クラス宣言のあとにある { と } の内側はすべて、クラスの状態と動作を定義しています。
クラスには、5 つの "" があります。 最初の 3 つは "プロパティ" です。 プロパティはデータ要素であり、検証やその他の規則を適用するコードを持つことができます。 最後の 2 つは "メソッド" です。 メソッドは 1 つの機能を実行するコード ブロックです。 各メンバーの名前を確認すると、開発者がそのクラスの作用を把握するための十分な情報が得られます。
新しいアカウントを開く
実装する最初の機能は、銀行口座を開く機能です。 顧客が口座を開く場合、初期残高や口座の (1 名または複数名の) 所有者の情報を入力する必要があります。
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!"); の 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 桁の数字で始めることです。 そして、新しい口座番号が作成されるごとに値を 1 増加します。 最後に、オブジェクトが作成されるときに現在の口座番号を格納します。
BankAccountクラスにメンバーの宣言を追加します。
{ クラスの先頭の左中かっこ BankAccount の後に、次のコードを配置します。
private static int s_accountNumberSeed = 1234567890;
accountNumberSeed はデータ メンバーです。 これは private であり、BankAccount クラス内のコードのみがこれにアクセスできます。 この方法により、プライベートな実装 (口座番号の生成方法) から (口座番号を持つなどの) パブリックな責任を分離できます。 また、 staticです。つまり、すべての BankAccount オブジェクトがこの変数の同じ単一インスタンスを共有します。 静的でない変数の値は BankAccount オブジェクトのインスタンスごとに一意です。
accountNumberSeedは private static フィールドであるため、C# の名前付け規則に従って s_ プレフィックスが付けられます。
s は static フィールドを示し、_ は private フィールドを示します。 各アカウント番号を初期化するには、コンストラクターに次の 2 行を追加します。 それらは this.Balance = initialBalance という行の後に配置します。
Number = s_accountNumberSeed.ToString();
s_accountNumberSeed++;
「dotnet run」と入力して結果を表示します。
預金と引き出しを作成する
銀行口座クラスは、預金と引き出しを許可して正しく動作するようにする必要があります。 口座のすべてのトランザクションを記録する履歴を作成して、預金と引き出しを実装しましょう。 すべてのトランザクションを追跡すると、単純にトランザクションごとに残高を更新する方法に比べていくつかのメリットがあります。 履歴は、すべてのトランザクションを監査して毎日の残高を管理するために使用できます。 必要に応じてすべてのトランザクションの履歴から残高を計算することにより、1 つのトランザクションの中で修正されたすべてのエラーが正しく残高に反映されて次の計算に使用されます。
まず、トランザクションを表す新しい型を作成します。 トランザクションは、何の責任も持たない 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; } の行を、次のコードに置き換えます。
public decimal Balance
{
get
{
decimal balance = 0;
foreach (var item in _allTransactions)
{
balance += item.Amount;
}
return balance;
}
}
この例は、"プロパティ" の重要な側面を示しています。 これで、別のプログラマーが値を要求したときに残高が計算されるようになりました。 この計算処理は、すべてのトランザクションを列挙して、その合計値を現在の残高として提供します。
次に MakeDeposit メソッドと MakeWithdrawal メソッドを実装します。 これらの方法では、最終的な 2 つのルールが適用されます。初期残高は正である必要があり、引き出しによって負の残高を作成することはできません。
これらの規則により、"例外" の概念が導入されています。 メソッドが作業を正常に完了できないことを示す標準的な方法は、例外をスローすることです。 例外の型とそれに関連付けられたメッセージがエラーを説明します。
MakeDeposit メソッドは、預金額が 0 以下になる場合に例外をスローします。
MakeWithdrawal メソッドは、引き出し額が 0 以下になる場合、または引き出しを適用した結果、残高が負の値になる場合に例外をスローします。
_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 ブロックを追加して、このコードを少し後でテストします。
残高を直接更新するのではなく、最初のトランザクションを追加するようにするため、コンストラクターを 1 か所変更する必要があります。 既に 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;
}
try-catch ステートメントを使用して、例外をスローする可能性があるコード ブロックをマークし、予想されるエラーをキャッチします。 同じ方法で、残高が負の値になっている場合に例外をスローするコードをテストします。
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」と入力して試します。
課題 - すべてのトランザクションをログに記録する
このチュートリアルを完了すると、トランザクション履歴の GetAccountHistory を作成する string メソッドを記述できるようになります。 このメソッドを 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 クラスを使って、各トランザクションを 1 行で表す文を含んだ文字列をフォーマットします。 これらのチュートリアルでは、前に文字列の書式設定を使用しました。 新しい文字の 1 つは \t です。 これはタブを挿入して出力をフォーマットします。
次の行を追加して、Program.cs でテストします。
Console.WriteLine(account.GetAccountHistory());
プログラムを実行し、結果を確認します。
次の手順
うまくいかない場合は、このチュートリアルのソースを GitHub リポジトリで確認できます。
オブジェクト指向プログラミングのチュートリアルに進むことができます。
次の記事でこれらの概念の詳細を学習できます。
.NET