クラスは自分で定義する型で、データ(フィールド)と、そのデータを扱うコード(メソッド)をまとめます。オブジェクトはクラスの1つのインスタンスで、実行時に new で作られます。1つのクラスから多くのオブジェクトができ、それぞれが独自の値を持ちます。
クラスの宣言とオブジェクトの作成
クラスの宣言は、波かっこの中にメンバーを並べます。フィールドはデータを、メソッドは振る舞いを保持します。new ClassName() はオブジェクトを確保し、それへの参照を返します。
出力:
Maya: 120.50
Omar: 40
Withdraw 200 succeeded: False
各オブジェクトは独自の Owner と Balance を持ちます。メソッドの中の Balance は「このメソッドが呼び出された対象のオブジェクトの残高」を意味するので、maya.Deposit と omar.Deposit は別の数値を変更します。その暗黙のオブジェクトは、名前で呼ぶ必要があるときは this として使えます。
代入しないフィールドは既定値から始まります。数値なら 0、bool なら false、文字列やその他のクラス型なら null です。
ここでのメンバーは、Main から届くように public にしています。実際のクラスでは通常フィールドを private にし、代わりにプロパティを公開します。そのルールはアクセス修飾子のページにあります。クラスは、オブジェクトの作成時にフィールドを設定するコンストラクターを宣言することもでき、これはコンストラクターのページで扱います。
クラスは参照型
クラス型の変数はオブジェクトを含んでいません。ヒープ上にあるオブジェクトへの参照を含んでいます。変数を代入すると参照がコピーされるので、2つの変数が同じオブジェクトを指すことがあります。
出力:
5
6
True
False
ここから3つのことがわかります。
secondを通してオブジェクトを変更すると、firstからもそれが見えます。オブジェクトは1つしかないからです。- オブジェクトをメソッドに渡すと、参照が渡されます。メソッドはオブジェクトのフィールドを変更でき、呼び出し側からもそれが見えます。(メソッドの中で引数に新しいオブジェクトを代入しても、呼び出し側の変数には影響しません。それには
refが必要です。) - 2つのクラスのインスタンスに対する
==は、既定では参照を比較します。「同じ値を持っているか」ではなく「同じオブジェクトか」です。lookalikeはまったく同じデータを持っていますが、別のオブジェクトです。
代入時のコピーと値による比較が必要なら、値型である構造体か、値で比較されるレコードを検討します。
nullとNullReferenceException
参照の変数は、「オブジェクトがない」ことを意味する null を保持できます。null を通してメンバーを使うと、C#のコードで最もよくある実行時の例外である NullReferenceException が投げられます。
出力:
True
no city on file
NullReferenceException: Address was never set
Lisbon
直し方が例外をキャッチすることであることは、ほぼありません。参照がなぜ null なのかを突き止め、オブジェクトをもっと早く作るか、使う前に確認するか、例外を投げる代わりに止まって null を返すnull条件演算子 ?. を使います。その後に ?? で代替値を与えます。どちらの演算子もnull許容型で扱います。
ToStringのオーバーライド
すべてのクラスは ToString() を提供する object を継承しています。継承した版は型の完全な名前(名前空間があればそれも含む)を返しますが、表示したいのはたいていそれではありません。オーバーライドします。
出力:
Plain
Mug (8.50)
In cart: Mug (8.50)
Total line: Mug (8.50)
Console.WriteLine、文字列補間、文字列との + は、すべて ToString() を呼んでくれます。デバッガーもそれを表示するので、自分でオブジェクトを表示しない場合でもオーバーライドは役に立ちます。
オブジェクト初期化子
オブジェクト初期化子は、オブジェクトが構築された直後に、同じ式の中で公開フィールドやプロパティを設定します。代入の羅列より読みやすく、コレクションのリテラルの中でもうまく使えます。
出力:
A12: 25 (0 extras)
A13: 40 (2 extras)
2枚目のチケットには2つの細部があります。オブジェクト初期化子はオブジェクトが作られた後に実行されるので、Price = 40m はフィールド初期化子の 25m を上書きします。Extras = { "Parking", "Poster" } には new がありません。フィールドがすでに保持しているリストを置き換えるのではなく、そのリストの Add を呼びます。
オブジェクト初期化子はコンストラクターではありません。設定できるのは呼び出し側のコードからアクセスできるメンバーだけで、メンバーが設定されることを強制もできません。特定の値がないとオブジェクトが不正になるなら、コンストラクターでそれらを必須にします。
部分クラス
partial キーワードは、1つのクラスを複数のファイルに分けます。コンパイラが各部分を1つのクラスにまとめます。
// Invoice.cs
public partial class Invoice
{
public decimal Subtotal;
public decimal Total() => Subtotal + Tax();
}
// Invoice.Tax.cs
public partial class Invoice
{
private decimal Tax() => Subtotal * 0.2m;
}
すべての部分に partial を付け、同じ名前空間と同じプロジェクトに置かなければなりません。よくある用途は自動生成されるコードです。WinFormsのデザイナー、ソースジェネレーター、一部のORMが一方の部分を書き、もう一方をあなたに任せるので、再生成しても自分のコードが上書きされることはありません。手書きのクラスを短くするためだけに複数のファイルに分けると、たいてい2つのクラスにすべきクラスを隠すことになります。
よくある間違い
- 作る前にオブジェクトを使う。 クラス型のフィールドは、
new ...を代入するまでnullです。最初のメンバーアクセスでNullReferenceExceptionが投げられます。 - 代入でコピーされると思う。 クラスでの
var b = a;は、同じオブジェクトに2つ目の名前を付けるだけです。独立したものが必要なら、新しいオブジェクトを作って値をコピーします。 ==でオブジェクトを比較して値の比較を期待する。 クラスでは、(stringのように)クラスが==をオーバーロードしていない限り参照を比較します。関係するフィールドを比較するか、Equalsをオーバーライドします。- オブジェクトを表示すると型名が出る。
ToString()をオーバーライドします。 - すべてを
publicにする。 小さな例では動きますが、どんなコードでもオブジェクトを不正な状態(たとえば負の残高)にできてしまいます。フィールドはprivateにし、ルールを確認するメソッドを通して変更します。
よくある質問
C#のクラスとは何ですか?
クラスは自分で定義する型で、データ(フィールドとプロパティ)と振る舞い(メソッド)に名前を付けてまとめたものです。class Account { public decimal Balance; public void Deposit(decimal amount) { Balance += amount; } } で1つ宣言できます。クラスは設計図で、new でオブジェクトを作るまで何もしません。
C#のクラスとオブジェクトの違いは何ですか?
クラスは定義で、ソースコードに1回書きます。オブジェクトはその1つのインスタンスで、実行時に new で作られ、すべてのインスタンスフィールドの独自のコピーを持ちます。new Account() を2回実行すると、残高が独立した同じクラスの2つのオブジェクトができます。
C#でオブジェクトを作るには?
new の後にクラス名とコンストラクターの引数を書きます:var account = new Account();。オブジェクト初期化子を使えば、同じ式の中で公開フィールドやプロパティを設定できます:new Account { Owner = "Maya" }。変数が保持するのは、オブジェクトそのものではなく、新しいオブジェクトへの参照です。
C#でNullReferenceExceptionが出るのはなぜですか?
null を保持する変数を通してメンバー(.Name、.Length、メソッドの呼び出し)を使ったので、処理の対象となるオブジェクトがないからです。よくある原因は、一度も代入されていないフィールド(クラスのフィールドは null から始まります)、null を返した検索、失敗時に null を返したメソッドです。if (x != null) で確認するか、x?.Name を使うか、使う前にオブジェクトが作られていることを確かめます。
C#でToStringをオーバーライドするには?
クラスで public override string ToString() を宣言し、表示したいテキストを返します。たとえば public override string ToString() => $"{Name} ({Price:F2})"; です。オーバーライドしないと、Console.WriteLine(obj) や文字列補間はクラス名を表示します。それが object.ToString() の返すものだからです。