C# のすべてのクラスは、明示的に継承元を指定しない場合、暗黙的に System.Object クラスから派生します。System.Object は .NET の型階層の頂点 に位置するクラスで、「基底クラス(Base Class)」あるいは「ルートクラス」と呼ばれます。「スーパー親クラス」という俗称で呼ばれることもあります。
// この 2 つは等価
public class MyClass { }
public class MyClass : System.Object { }
object キーワードは System.Object の C# エイリアスです(string が System.String のエイリアスであるのと同様です)。
System.Object の役割
System.Object が提供する主な役割は次の 3 つです。
- 共通インターフェースの提供 —
Equals・GetHashCode・ToStringなどのメソッドをすべての型に保証します - ポリモーフィズムの基盤 —
object型の変数にあらゆる型のインスタンスを代入できます(ボックス化) - リフレクションの起点 —
GetType()でランタイム型情報を取得できます
メソッド一覧
| メソッド | 種類 | 説明 |
|---|---|---|
ToString() |
virtual | オブジェクトの文字列表現を返す |
Equals(object?) |
virtual | 等値比較を行う |
GetHashCode() |
virtual | ハッシュコードを返す |
GetType() |
非 virtual | ランタイムの型情報を返す |
MemberwiseClone() |
protected | シャローコピーを作成する |
ReferenceEquals(object?, object?) |
static | 参照の同一性を比較する |
Finalize() |
protected virtual | ガベージコレクション前の処理(C# では ~デストラクタ 構文) |
ToString() — 文字列表現
デフォルトでは型の完全修飾名(名前空間.クラス名)を返します。意味のある文字列を返すようにオーバーライドするのが一般的です。
var p = new Person { Name = "Alice", Age = 30 };
Console.WriteLine(p.ToString()); // → "名前空間.Person"(デフォルト)
オーバーライドの例
public class Person
{
public string Name { get; set; } = "";
public int Age { get; set; }
public override string ToString()
=> $"Person {{ Name = {Name}, Age = {Age} }}";
}
var p = new Person { Name = "Alice", Age = 30 };
Console.WriteLine(p); // → Person { Name = Alice, Age = 30 }
Console.WriteLine や文字列補間 $"{obj}" は内部で ToString() を呼び出すため、オーバーライドするとデバッグや出力で非常に役立ちます。
Equals() — 等値比較
デフォルトの Equals は参照の同一性(同じインスタンスかどうか)を比較します。値の等値比較を行いたい場合はオーバーライドが必要です。
var a = new Person { Name = "Alice", Age = 30 };
var b = new Person { Name = "Alice", Age = 30 };
Console.WriteLine(a.Equals(b)); // False(別のインスタンス)
Console.WriteLine(a == b); // False(同上)
オーバーライドの例
public class Person
{
public string Name { get; set; } = "";
public int Age { get; set; }
public override bool Equals(object? obj)
{
if (obj is not Person other) return false;
return Name == other.Name && Age == other.Age;
}
public override int GetHashCode()
=> HashCode.Combine(Name, Age);
}
var a = new Person { Name = "Alice", Age = 30 };
var b = new Person { Name = "Alice", Age = 30 };
Console.WriteLine(a.Equals(b)); // True
Equals オーバーライドの注意点
Equals をオーバーライドする場合は 必ず GetHashCode も同時にオーバーライドしてください。これは、.NET の規約「等値なオブジェクトは同じハッシュコードを持たなければならない」を守るためです。守らないと Dictionary や HashSet が正しく動作しません。
// NG: Equals をオーバーライドしたが GetHashCode はデフォルトのまま
var dict = new Dictionary<Person, string>();
dict[new Person { Name = "Alice", Age = 30 }] = "エンジニア";
// 別のインスタンスだがハッシュコードが異なるためキーが見つからない
var key = new Person { Name = "Alice", Age = 30 };
Console.WriteLine(dict.ContainsKey(key)); // False(意図しない結果)
GetHashCode() — ハッシュコード
Dictionary<TKey, TValue> や HashSet<T> などのハッシュベースのコレクションが内部でキーを管理するために使用します。
実装の指針
- 同じ値を持つオブジェクトは同じハッシュコードを返す(必須)
- 異なる値はできるだけ異なるハッシュコードを返す(衝突を減らすため)
HashCode.Combine()を使うと簡潔かつ品質の高いハッシュ値を生成できます
public override int GetHashCode()
=> HashCode.Combine(Name, Age); // .NET Core 2.1 以降推奨
GetType() — 型情報の取得
オブジェクトのランタイム型を表す Type インスタンスを返します。オーバーライドできません。
object obj = "Hello";
Type t = obj.GetType();
Console.WriteLine(t.Name); // String
Console.WriteLine(t.FullName); // System.String
Console.WriteLine(t.IsValueType); // False
型チェックに使うこともできますが、is 演算子の方が簡潔で派生型も考慮されます。
// GetType による型チェック(派生型は false になる)
if (obj.GetType() == typeof(string)) { ... }
// is による型チェック(派生型も true になる)
if (obj is string) { ... }
ReferenceEquals() — 参照の同一性
Equals が値ベースにオーバーライドされていても、参照が同一かどうかを確認できます。static メソッドなので、null の比較にも安全に使えます。
var a = new Person { Name = "Alice", Age = 30 };
var b = a; // 同じ参照
var c = new Person { Name = "Alice", Age = 30 };
Console.WriteLine(ReferenceEquals(a, b)); // True(同じ参照)
Console.WriteLine(ReferenceEquals(a, c)); // False(別のインスタンス)
null チェックにも使えます。
string? s = null;
Console.WriteLine(ReferenceEquals(s, null)); // True
MemberwiseClone() — シャローコピー
protected メソッドなので、クラス内部からしか呼び出せません。フィールドをそのままコピーした新しいインスタンス(シャローコピー)を返します。
public class Person
{
public string Name { get; set; } = "";
public int Age { get; set; }
public Person Clone() => (Person)MemberwiseClone();
}
var original = new Person { Name = "Alice", Age = 30 };
var copy = original.Clone();
Console.WriteLine(ReferenceEquals(original, copy)); // False(別インスタンス)
Console.WriteLine(copy.Name); // Alice
注意: シャローコピーは参照型フィールドのコピーを行いません。参照型フィールドは元のオブジェクトと同じ参照を指したままになります(ディープコピーにはなりません)。
public class Order
{
public List<string> Items { get; set; } = new();
public Order Clone() => (Order)MemberwiseClone();
}
var o1 = new Order();
o1.Items.Add("Apple");
var o2 = o1.Clone();
o2.Items.Add("Banana"); // o1.Items にも反映される!
Console.WriteLine(string.Join(", ", o1.Items)); // Apple, Banana
object 型への代入(ボックス化)
object 型にはあらゆる型のインスタンスを代入できます。
object obj = "Hello";
object num = 42; // 値型はボックス化される
object flag = true;
値型(int, bool, struct など)を object 型に代入すると**ボックス化(boxing)**が発生し、ヒープ上にコピーが作成されます。パフォーマンスが重要な箇所では多用を避けてください。
int value = 42;
object boxed = value; // ボックス化(ヒープにコピー)
int unboxed = (int)boxed; // アンボックス化(型を明示する必要がある)
まとめ
| メソッド | オーバーライド | よくある用途 |
|---|---|---|
ToString() |
推奨 | デバッグ出力、ログ、$"..." 補間 |
Equals() |
値等値が必要なら必須 | コレクションのキー比較、== の挙動変更 |
GetHashCode() |
Equals と常にセット |
Dictionary・HashSet の正確な動作 |
GetType() |
不可 | リフレクション、型の確認 |
ReferenceEquals() |
不可(static) | 参照の同一性確認、null チェック |
MemberwiseClone() |
通常不要 | クローンメソッドの実装 |
Equals と GetHashCode は常にセットでオーバーライドすること、MemberwiseClone はシャローコピーであることの 2 点が特に重要な注意点です。