C#のobject型とは?クラス・インスタンスとの違いから使いどころまで初心者向けに解説

はじめに

C#を学び始めると、object型という言葉をよく見かけます。

C#
object value = "Hello";
object number = 100;
object person = new Person();

このように、object型には文字列、数値、クラスのインスタンスなど、さまざまな値を代入できます。

しかし初心者のうちは、次のような疑問を持ちやすいです。

「object型とは何なのか」
「クラスやインスタンスとは何が違うのか」
「なぜ何でも代入できるのか」
「便利そうだけど、いつ使えばいいのか」
「object型を使うと危険な場面はあるのか」

結論からいうと、C#のobject型はすべての型の元になる最も基本的な型です。

ただし、何でも入れられるからといって、いつでもobject型を使えばよいわけではありません。object型を多用すると、型安全性が下がったり、キャストミスによる例外が発生したり、コードが読みにくくなったりします。

この記事では、C#のobject型について、クラス・インスタンスとの違い、基本的な使い方、型変換、ボックス化、注意点、vardynamicやジェネリクスとの違いまで、初心者向けにわかりやすく解説します。

1. C#のobject型とは?まず結論を初心者向けに解説

1-1. object型は「すべての型の基底型」

C#のobject型は、すべての型の基底型です。

基底型とは、他の型の元になる型のことです。C#では、文字列を表すstring、整数を表すint、自分で作成したクラスなど、あらゆる型が最終的にobject型を基にしています。

たとえば、次のようにさまざまな値をobject型の変数に代入できます。

C#
object text = "こんにちは";
object number = 123;
object flag = true;
object date = DateTime.Now;

stringintboolDateTimeも、本来は別々の型です。それでもobject型の変数に入れられるのは、C#のすべての型がobject型を基にしているためです。

1-2. objectはSystem.Objectの別名

C#のobjectは、正確にはSystem.Objectの別名です。

次の2つは同じ意味です。

C#
object value1 = "Hello";
System.Object value2 = "Hello";

C#では、よく使う型に対して短いキーワードが用意されています。

たとえば、intSystem.Int32の別名、stringSystem.Stringの別名、boolSystem.Booleanの別名です。

同じように、objectSystem.Objectの別名です。

C#
object value = "C#";
Console.WriteLine(value.GetType());

このコードを実行すると、実際の中身の型であるSystem.Stringが表示されます。

1-3. object型でできること・できないこと

object型でできることは、主に次のようなものです。

C#
object value = "Hello";

Console.WriteLine(value.ToString());
Console.WriteLine(value.GetType());

object型の変数では、すべての型に共通する基本的なメソッドを呼び出せます。

代表的なものは、ToStringEqualsGetHashCodeGetTypeなどです。

一方で、object型のままでは、元の型に固有のメソッドやプロパティは呼び出せません。

C#
object value = "Hello";

// コンパイルエラー
// Console.WriteLine(value.Length);

valueの中身は文字列ですが、変数の型はobjectです。そのため、C#コンパイラは「これは文字列である」と判断できません。

文字列として扱いたい場合は、キャストや型判定が必要です。

C#
object value = "Hello";

string text = (string)value;
Console.WriteLine(text.Length);

1-4. この記事で理解できること

この記事を読むと、次の内容が理解できます。

object型が何を表すのか、クラスやインスタンスとどう違うのか、なぜさまざまな型を代入できるのか、object型から元の型に戻す方法、ボックス化とボックス化解除の仕組み、object型を使うべき場面と避けるべき場面、vardynamicやジェネリクスとの違いを順番に理解できます。

特に初心者がつまずきやすい「object型」と「オブジェクト」という言葉の違いも整理して解説します。

2. C#における「object」「クラス」「インスタンス」の違い

2-1. クラスとは設計図のこと

クラスとは、データや処理をまとめるための設計図です。

たとえば、人を表すPersonクラスを考えてみます。

C#
class Person
{
public string Name { get; set; }
public int Age { get; set; }

public void SayHello()
{
Console.WriteLine($"こんにちは、私は{Name}です。");
}
}

このPersonクラスは、「人には名前と年齢があり、あいさつできる」という設計を表しています。

ただし、クラスを定義しただけでは、実際の人のデータはまだ存在しません。あくまで設計図を作っただけです。

2-2. インスタンスとはクラスから作られた実体のこと

インスタンスとは、クラスという設計図から作られた実体のことです。

C#
Person person = new Person();
person.Name = "佐藤";
person.Age = 25;
person.SayHello();

このコードでは、new Person()によってPersonクラスのインスタンスを作成しています。

Personクラスが設計図であり、personがその設計図から作られた実体です。

同じクラスから複数のインスタンスを作ることもできます。

C#
Person person1 = new Person();
person1.Name = "佐藤";

Person person2 = new Person();
person2.Name = "鈴木";

person1person2は、どちらもPersonクラスから作られたインスタンスですが、別々の実体です。

2-3. オブジェクトとはメモリ上に存在する実体のこと

プログラミングにおけるオブジェクトとは、一般的にはメモリ上に存在する実体を指します。

C#では、クラスから作成されたインスタンスをオブジェクトと呼ぶことが多いです。

C#
Person person = new Person();

この場合、new Person()によって作られた実体がオブジェクトです。

ただし、「オブジェクト」という言葉は文脈によって少し意味が変わります。広い意味では、プログラム上で扱うデータのまとまりをオブジェクトと呼ぶこともあります。

2-4. object型と「オブジェクト」という言葉の違い

初心者が混同しやすいのが、object型と「オブジェクト」という言葉の違いです。

object型は、C#に存在する具体的な型の名前です。

C#
object value = "Hello";

一方で、「オブジェクト」は、メモリ上に存在する実体を表す一般的な言葉です。

つまり、object型は型名であり、「オブジェクト」は概念です。

たとえば、次のコードを見てみます。

C#
Person person = new Person();

この場合、personの型はPersonです。しかし、new Person()で作られた実体はオブジェクトと呼べます。

一方、次のように書くこともできます。

C#
object obj = new Person();

この場合、変数objの型はobjectです。しかし、中身として入っている実体はPersonクラスのインスタンスです。

2-5. 初心者が混同しやすい用語を整理

C#を学ぶうえで、次のように整理すると理解しやすくなります。

クラスは設計図です。インスタンスはクラスから作られた実体です。オブジェクトはメモリ上に存在する実体を指す一般的な言葉です。object型はC#のすべての型の基底型です。

たとえば、Personクラスを定義し、new Person()でインスタンスを作成し、それをobject型の変数に代入できます。

C#
class Person
{
public string Name { get; set; }
}

Person person = new Person();
object obj = person;

このとき、personobjも同じ実体を参照しています。ただし、変数の型が違います。

personPerson型なのでNameプロパティを使えます。

C#
person.Name = "田中";

しかし、objobject型なので、そのままではNameプロパティを使えません。

C#
// コンパイルエラー
// obj.Name = "田中";

object型の変数として扱っている間は、object型に定義されている基本的な機能しか使えないのです。

3. object型の基本的な使い方

3-1. object型の変数に文字列・数値・インスタンスを代入する

object型の変数には、さまざまな型の値を代入できます。

C#
object value1 = "Hello";
object value2 = 123;
object value3 = 3.14;
object value4 = true;
object value5 = new Person();

このように、文字列、整数、小数、真偽値、自作クラスのインスタンスなどを代入できます。

ただし、object型の変数に入れたからといって、元の型の機能をそのまま使えるわけではありません。

C#
object value = "Hello";

// コンパイルエラー
// Console.WriteLine(value.Length);

文字列のLengthプロパティを使いたい場合は、文字列型に戻す必要があります。

C#
string text = (string)value;
Console.WriteLine(text.Length);

3-2. 参照型をobject型に代入する例

クラスや文字列などの参照型は、object型に代入できます。

C#
class Person
{
public string Name { get; set; }
}

Person person = new Person();
person.Name = "山田";

object obj = person;

この場合、objにはPerson型のインスタンスが入っています。

ただし、変数objの型はobjectなので、次のようにNameプロパティへ直接アクセスすることはできません。

C#
// コンパイルエラー
// Console.WriteLine(obj.Name);

Person型として扱いたい場合は、キャストします。

C#
Person p = (Person)obj;
Console.WriteLine(p.Name);

参照型をobject型に代入しても、実体そのものが別のものに変わるわけではありません。変数の見え方がobject型になるだけです。

3-3. 値型をobject型に代入する例

intdoubleboolなどの値型も、object型に代入できます。

C#
int number = 100;
object obj = number;

このとき、値型であるintobject型として扱えるようになります。

ただし、値型をobject型に代入すると、ボックス化という処理が行われます。

C#
int number = 100;
object obj = number; // ボックス化

ボックス化については後ほど詳しく解説しますが、簡単にいうと、値型の値を参照型のように扱うために包み込む処理です。

3-4. object型から元の型として取り出す方法

object型の変数から元の型として値を取り出すには、キャストを使います。

C#
object obj = "Hello";

string text = (string)obj;
Console.WriteLine(text);

数値の場合も同じです。

C#
object obj = 100;

int number = (int)obj;
Console.WriteLine(number);

ただし、間違った型にキャストすると例外が発生します。

C#
object obj = "Hello";

// 実行時エラー
// int number = (int)obj;

この場合、objの中身は文字列なのに、int型として取り出そうとしているため、InvalidCastExceptionが発生します。

安全に取り出したい場合は、is演算子やパターンマッチングを使います。

C#
object obj = "Hello";

if (obj is string text)
{
Console.WriteLine(text.Length);
}

4. object型と型変換の関係

4-1. 暗黙的にobject型へ変換できる理由

C#では、多くの型をobject型へ暗黙的に変換できます。

C#
string text = "Hello";
object obj = text;

このコードでは、明示的なキャストを書かなくてもstring型の値をobject型に代入できます。

これは、string型がobject型を基にしているためです。自作クラスでも同じです。

C#
Person person = new Person();
object obj = person;

値型の場合も、object型に代入できます。

C#
int number = 10;
object obj = number;

ただし、値型の場合はボックス化が行われます。

参照型の場合は、同じ実体をobject型として参照します。値型の場合は、値を箱に入れるような処理が行われ、object型として扱える形になります。

4-2. object型から元の型へ戻すにはキャストが必要

object型から元の型へ戻す場合は、明示的なキャストが必要です。

C#
object obj = "Hello";

string text = (string)obj;

なぜなら、object型の変数にはさまざまな型が入る可能性があるからです。

C#
object obj = 123;

// これは正しい
int number = (int)obj;

// これは間違い
// string text = (string)obj;

コンパイラは、object型の中に実際に何が入っているかを完全には判断できません。そのため、開発者が「この中身はこの型である」と明示する必要があります。

4-3. is演算子で型を判定する

is演算子を使うと、object型の中身が特定の型かどうかを判定できます。

C#
object obj = "Hello";

if (obj is string)
{
Console.WriteLine("文字列です");
}

さらに、C#ではパターンマッチングを使って、型判定と変数への代入を同時に行えます。

C#
object obj = "Hello";

if (obj is string text)
{
Console.WriteLine(text.Length);
}

この書き方では、objstring型だった場合だけ、textというstring型の変数として扱えます。

数値の場合も同じです。

C#
object obj = 100;

if (obj is int number)
{
Console.WriteLine(number + 50);
}

安全に型を確認してから処理できるため、初心者にもおすすめの書き方です。

4-4. as演算子で安全に変換する

as演算子を使うと、キャストに失敗した場合に例外を発生させず、nullを返すことができます。

C#
object obj = "Hello";

string text = obj as string;

if (text != null)
{
Console.WriteLine(text.Length);
}

通常のキャストでは、型が違うとInvalidCastExceptionが発生します。

C#
object obj = 123;

// 実行時エラー
// string text = (string)obj;

一方、as演算子を使うと、変換できない場合はnullになります。

C#
object obj = 123;

string text = obj as string;

if (text == null)
{
Console.WriteLine("文字列ではありません");
}

ただし、as演算子は主に参照型やnull許容型に対して使います。intのような通常の値型にはそのまま使えません。

C#
object obj = 123;

// コンパイルエラー
// int number = obj as int;

値型を安全に取り出す場合は、is演算子やパターンマッチングを使うのが一般的です。

4-5. パターンマッチングで安全に値を取り出す

最近のC#では、パターンマッチングを使うことで、object型の値を安全かつ読みやすく処理できます。

C#
object obj = "Hello";

if (obj is string text)
{
Console.WriteLine($"文字列の長さ: {text.Length}");
}
else if (obj is int number)
{
Console.WriteLine($"数値: {number}");
}
else
{
Console.WriteLine("その他の型です");
}

switch文やswitch式でも使えます。

C#
object obj = 123;

switch (obj)
{
case string text:
Console.WriteLine($"文字列: {text}");
break;

case int number:
Console.WriteLine($"整数: {number}");
break;

case bool flag:
Console.WriteLine($"真偽値: {flag}");
break;

default:
Console.WriteLine("不明な型です");
break;
}

このように、object型の中身が複数の型になる可能性がある場合は、パターンマッチングを使うと安全に分岐できます。

5. object型とボックス化・ボックス化解除

5-1. 値型をobject型に入れるとボックス化が起きる

C#には、大きく分けて値型と参照型があります。

intdoubleboolstructなどは値型です。一方、classstring、配列などは参照型です。

値型をobject型に代入すると、ボックス化が起きます。

C#
int number = 10;
object obj = number; // ボックス化

ボックス化とは、値型の値をobject型として扱えるように、ヒープ領域に包み込む処理です。

簡単にいうと、値型の値を箱に入れて、参照型のように扱える状態にするイメージです。

5-2. object型から値型に戻すとボックス化解除が起きる

object型に入れた値型を、元の値型として取り出すときには、ボックス化解除が起きます。

C#
object obj = 10;

int number = (int)obj; // ボックス化解除

ボックス化解除では、箱に入っている値を取り出して、元の値型として扱います。

ただし、取り出す型を間違えると例外が発生します。

C#
object obj = 10;

// 実行時エラー
// long number = (long)obj;

objの中身はintとしてボックス化されています。そのため、longとして直接取り出すことはできません。

この場合は、まずintとして取り出してから変換します。

C#
object obj = 10;

int intValue = (int)obj;
long longValue = intValue;

5-3. ボックス化・ボックス化解除のサンプルコード

ボックス化とボックス化解除の流れをサンプルコードで確認してみましょう。

C#
int number = 100;

// ボックス化
object obj = number;

// ボックス化解除
int result = (int)obj;

Console.WriteLine(result);

このコードでは、まずint型のnumberobject型のobjに代入しています。この時点でボックス化が起きます。

次に、object型のobjint型のresultに戻しています。この時点でボックス化解除が起きます。

もう少しわかりやすく、値のコピーに注目してみます。

C#
int number = 100;
object obj = number;

number = 200;

Console.WriteLine(number);
Console.WriteLine(obj);

実行結果は次のようになります。

C#
200
100

numberobject型に代入した時点で、値がコピーされてボックス化されます。その後に元のnumberを変更しても、ボックス化されたobjの値は変わりません。

5-4. パフォーマンス面で注意すべきポイント

ボックス化とボックス化解除は、通常の代入よりもコストがかかります。

少し使う程度であれば大きな問題になることは多くありませんが、大量のデータを繰り返し処理する場面では注意が必要です。

たとえば、object型のリストに大量の整数を入れると、整数を追加するたびにボックス化が発生します。

C#
List<object> values = new List<object>();

for (int i = 0; i < 100000; i++)
{
values.Add(i); // intがobjectにボックス化される
}

取り出すときにもボックス化解除が必要です。

C#
int total = 0;

foreach (object value in values)
{
total += (int)value; // ボックス化解除
}

このような場合は、List<int>のようにジェネリクスを使うほうが適しています。

C#
List<int> values = new List<int>();

for (int i = 0; i < 100000; i++)
{
values.Add(i);
}

List<int>であれば、int型の値をそのまま扱えるため、不要なボックス化を避けられます。

6. object型で使える代表的なメソッド

6-1. ToStringメソッド

ToStringメソッドは、オブジェクトを文字列として表現するためのメソッドです。

C#
object value = 123;

Console.WriteLine(value.ToString());

実行結果は次のようになります。

C#
123

ToStringは、ほとんどの型で使えます。

C#
object text = "Hello";
object number = 100;
object flag = true;

Console.WriteLine(text.ToString());
Console.WriteLine(number.ToString());
Console.WriteLine(flag.ToString());

自作クラスでは、ToStringをオーバーライドすることで表示内容を変更できます。

C#
class Person
{
public string Name { get; set; }

public override string ToString()
{
return $"名前: {Name}";
}
}
C#
Person person = new Person { Name = "佐藤" };
Console.WriteLine(person.ToString());

ToStringを適切に定義しておくと、ログ出力やデバッグ時に便利です。

6-2. Equalsメソッド

Equalsメソッドは、2つの値やオブジェクトが等しいかどうかを判定するためのメソッドです。

C#
object a = "Hello";
object b = "Hello";

Console.WriteLine(a.Equals(b));

実行結果は次のようになります。

C#
True

数値でも使えます。

C#
object a = 100;
object b = 100;

Console.WriteLine(a.Equals(b));

自作クラスの場合、何をもって等しいと判断するかは設計によって変わります。

C#
class Person
{
public string Name { get; set; }
}
C#
Person p1 = new Person { Name = "田中" };
Person p2 = new Person { Name = "田中" };

Console.WriteLine(p1.Equals(p2));

この場合、標準では別々のインスタンスとして扱われるため、Falseになることがあります。

名前が同じなら同じ人物として扱いたい場合は、Equalsをオーバーライドする設計が必要です。

6-3. GetHashCodeメソッド

GetHashCodeメソッドは、オブジェクトのハッシュコードを取得するメソッドです。

C#
object value = "Hello";

Console.WriteLine(value.GetHashCode());

ハッシュコードは、DictionaryHashSetなど、ハッシュを使うコレクションで利用されます。

C#
HashSet<string> names = new HashSet<string>();

names.Add("Alice");
names.Add("Bob");

通常のプログラミングでは、GetHashCodeを直接使う場面は多くありません。

ただし、自作クラスでEqualsをオーバーライドする場合は、GetHashCodeも合わせて適切にオーバーライドする必要があります。

C#
class Person
{
public string Name { get; set; }

public override bool Equals(object obj)
{
if (obj is Person other)
{
return Name == other.Name;
}

return false;
}

public override int GetHashCode()
{
return Name?.GetHashCode() ?? 0;
}
}

EqualsGetHashCodeの整合性が取れていないと、DictionaryHashSetで意図しない動作になることがあります。

6-4. GetTypeメソッド

GetTypeメソッドは、実際の型情報を取得するためのメソッドです。

C#
object value = "Hello";

Console.WriteLine(value.GetType());

実行結果は次のようになります。

C#
System.String

object型の変数に入っていても、実際の中身の型を調べることができます。

C#
object value1 = 100;
object value2 = true;
object value3 = DateTime.Now;

Console.WriteLine(value1.GetType());
Console.WriteLine(value2.GetType());
Console.WriteLine(value3.GetType());

実行結果の例は次のようになります。

C#
System.Int32
System.Boolean
System.DateTime

GetTypeは、デバッグや型の確認に便利です。

ただし、型ごとに処理を分けたい場合は、GetTypeで比較するよりも、is演算子やパターンマッチングを使うほうが読みやすいことが多いです。

6-5. ReferenceEqualsメソッド

ReferenceEqualsメソッドは、2つの変数が同じインスタンスを参照しているかどうかを判定します。

C#
Person p1 = new Person();
Person p2 = p1;

Console.WriteLine(object.ReferenceEquals(p1, p2));

実行結果は次のようになります。

C#
True

p2 = p1としているため、p1p2は同じインスタンスを参照しています。

一方、次のように別々にインスタンスを作成した場合は、内容が同じでも別の実体です。

C#
Person p1 = new Person { Name = "佐藤" };
Person p2 = new Person { Name = "佐藤" };

Console.WriteLine(object.ReferenceEquals(p1, p2));

実行結果は次のようになります。

C#
False

ReferenceEqualsは、値が等しいかどうかではなく、同じ参照先かどうかを判定します。

7. object型の使いどころ

7-1. どんな型でも受け取りたい場合

object型は、どんな型でも受け取りたい場合に使えます。

たとえば、ログ出力用のメソッドを考えてみます。

C#
void PrintValue(object value)
{
Console.WriteLine(value);
}

このメソッドには、文字列でも数値でも真偽値でも渡せます。

C#
PrintValue("Hello");
PrintValue(100);
PrintValue(true);

Console.WriteLineにも、objectを受け取るオーバーロードがあります。

C#
Console.WriteLine((object)"Hello");
Console.WriteLine((object)123);

どんな型でも文字列として出力したい、というような場面ではobject型が役立ちます。

7-2. 複数の型を一時的に扱いたい場合

複数の型を一時的にまとめて扱いたい場合にも、object型を使うことがあります。

C#
object[] values = new object[]
{
"田中",
25,
true,
DateTime.Now
};

このように、文字列、数値、真偽値、日付など、異なる型の値を1つの配列に入れられます。

取り出すときは、型を判定して処理します。

C#
foreach (object value in values)
{
if (value is string text)
{
Console.WriteLine($"文字列: {text}");
}
else if (value is int number)
{
Console.WriteLine($"整数: {number}");
}
else
{
Console.WriteLine($"その他: {value}");
}
}

ただし、複数の型を混在させる設計は、コードが複雑になりやすいです。長期的に使うデータ構造では、専用のクラスやジェネリクスを検討したほうがよい場合があります。

7-3. 古いAPIやライブラリでobject型が使われる場合

古いAPIや一部のライブラリでは、object型が使われていることがあります。

たとえば、昔のコレクションではobject型を使ってさまざまな値を格納していました。

C#
ArrayList list = new ArrayList();

list.Add("Hello");
list.Add(100);
list.Add(true);

ArrayListは、要素をobject型として扱います。そのため、取り出すときにキャストが必要です。

C#
string text = (string)list[0];
int number = (int)list[1];

現在のC#では、多くの場合、List<T>のようなジェネリクスを使います。

C#
List<string> names = new List<string>();

names.Add("Alice");
names.Add("Bob");

ジェネリクスを使えば、型安全にデータを扱えます。

7-4. object型を引数にするメソッドの例

object型を引数にすると、さまざまな型を受け取れる汎用的なメソッドを作れます。

C#
void ShowInfo(object value)
{
if (value == null)
{
Console.WriteLine("値はnullです");
return;
}

Console.WriteLine($"値: {value}");
Console.WriteLine($"型: {value.GetType()}");
}

使い方は次のとおりです。

C#
ShowInfo("Hello");
ShowInfo(123);
ShowInfo(DateTime.Now);
ShowInfo(null);

このようなメソッドは、値の内容や型を表示するデバッグ用の処理として便利です。

ただし、メソッド内で特定の型に依存する処理をたくさん書くようになると、見通しが悪くなります。

C#
void Process(object value)
{
if (value is string text)
{
Console.WriteLine(text.ToUpper());
}
else if (value is int number)
{
Console.WriteLine(number * 2);
}
else if (value is DateTime date)
{
Console.WriteLine(date.ToString("yyyy/MM/dd"));
}
}

このような処理が増える場合は、設計を見直したほうがよいことがあります。

7-5. object型の配列やコレクションを使う場面

object型の配列を使うと、異なる型の値をまとめて扱えます。

C#
object[] row = new object[]
{
1,
"商品A",
1200,
true
};

データベースの行、CSVの一時的な読み込み、ログ出力、テストデータなど、型がまだ確定していない一時的なデータを扱う場面では便利です。

ただし、業務ロジックの中心でobject[]を多用すると、何番目に何の値が入っているのか分かりにくくなります。

C#
int id = (int)row[0];
string name = (string)row[1];
int price = (int)row[2];
bool isActive = (bool)row[3];

このような場合は、専用のクラスを作ったほうが読みやすくなります。

C#
class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int Price { get; set; }
public bool IsActive { get; set; }
}
C#
Product product = new Product
{
Id = 1,
Name = "商品A",
Price = 1200,
IsActive = true
};

object型は便利ですが、データの意味が明確な場合は、専用の型を作るほうがよい設計になります。

8. object型を使うときの注意点

8-1. 型安全性が低くなる

object型を使うと、どんな型でも代入できる反面、型安全性が低くなります。

C#
object value = "Hello";

この変数には文字列が入っていますが、変数の型はobjectです。そのため、文字列として使うにはキャストが必要です。

C#
string text = (string)value;

もし中身が文字列ではなかった場合、実行時にエラーになります。

C#
object value = 100;

// 実行時エラー
// string text = (string)value;

string型の変数であれば、コンパイル時点で文字列として扱えることが保証されます。

C#
string text = "Hello";
Console.WriteLine(text.Length);

型が明確な場合は、最初から具体的な型を使うほうが安全です。

8-2. キャストミスで例外が発生する

object型から元の型に戻すとき、間違った型にキャストするとInvalidCastExceptionが発生します。

C#
object value = "Hello";

// InvalidCastException
int number = (int)value;

このようなミスを防ぐには、キャスト前に型を確認します。

C#
object value = "Hello";

if (value is int number)
{
Console.WriteLine(number);
}
else
{
Console.WriteLine("int型ではありません");
}

パターンマッチングを使うと、型チェックと取り出しを同時に行えるため安全です。

C#
object value = "Hello";

if (value is string text)
{
Console.WriteLine(text.ToUpper());
}

8-3. コードの可読性が下がりやすい

object型を多用すると、コードを読んだときに中身の型が分かりにくくなります。

C#
void Save(object data)
{
// dataに何が入っているのか分かりにくい
}

このメソッドだけを見ると、dataに文字列が入るのか、数値が入るのか、ユーザー情報が入るのか分かりません。

具体的な型を使えば、コードの意図が明確になります。

C#
void SaveUser(User user)
{
// Userを保存することが分かりやすい
}

複数の型を扱いたい場合でも、object型ではなく、共通のインターフェースや基底クラスを使えることがあります。

C#
interface ISavable
{
void Save();
}
C#
void Save(ISavable data)
{
data.Save();
}

このように設計すると、何でも受け取るのではなく、「保存できるものだけ受け取る」という意図を表せます。

8-4. ボックス化によるパフォーマンス低下に注意する

値型をobject型に代入すると、ボックス化が発生します。

C#
int number = 10;
object obj = number;

大量の値型をobject型として扱うと、ボックス化とボックス化解除が繰り返され、パフォーマンスに影響することがあります。

C#
List<object> values = new List<object>();

for (int i = 0; i < 100000; i++)
{
values.Add(i);
}

この場合、iint型ですが、object型のリストに追加されるたびにボックス化されます。

数値だけを扱うなら、List<int>を使うべきです。

C#
List<int> values = new List<int>();

for (int i = 0; i < 100000; i++)
{
values.Add(i);
}

List<int>であれば、型安全であり、ボックス化も避けられます。

8-5. object型を多用しないほうがよいケース

次のような場面では、object型を多用しないほうがよいです。

データの型が最初から分かっている場合、同じ型の値だけを扱うコレクションを作る場合、メソッドの引数として特定の型を期待している場合、値型を大量に扱う場合、キャストが何度も必要になる場合です。

たとえば、ユーザー名の一覧を扱うなら、List<object>ではなくList<string>を使うべきです。

C#
List<string> names = new List<string>();

names.Add("佐藤");
names.Add("鈴木");

商品の一覧を扱うなら、List<object>ではなくList<Product>を使うほうが自然です。

C#
List<Product> products = new List<Product>();

object型は、どうしても型を限定できない場面や、一時的に複数の型を扱いたい場面に絞って使うのが基本です。

9. object型とvar・dynamic・ジェネリクスの違い

9-1. object型とvarの違い

object型とvarは、見た目が似ている場面がありますが、意味は大きく違います。

C#
object value1 = "Hello";
var value2 = "Hello";

value1の型はobjectです。

一方、value2の型はコンパイラによってstringと推論されます。

つまり、varは型を曖昧にするものではありません。右辺から型を推論しているだけです。

C#
var text = "Hello";
Console.WriteLine(text.Length);

このコードでは、textstring型として扱われるため、Lengthプロパティを使えます。

しかし、object型の場合は使えません。

C#
object text = "Hello";

// コンパイルエラー
// Console.WriteLine(text.Length);

varは型安全性を保ったまま、型名の記述を省略する機能です。object型は、実際に変数の型がobjectになります。

9-2. object型とdynamicの違い

dynamicは、実行時にメンバーの呼び出しを解決する型です。

C#
dynamic value = "Hello";

Console.WriteLine(value.Length);

このコードはコンパイルできます。valueの中身が文字列であれば、実行時にLengthプロパティが呼び出されます。

一方、object型ではコンパイルエラーになります。

C#
object value = "Hello";

// コンパイルエラー
// Console.WriteLine(value.Length);

dynamicは便利に見えますが、実行時までエラーが分かりにくくなります。

C#
dynamic value = 123;

// 実行時エラー
Console.WriteLine(value.Length);

intにはLengthプロパティがないため、実行時にエラーになります。

objectはコンパイル時に安全性を確認しやすい一方で、使うにはキャストが必要です。dynamicは書きやすい反面、実行時エラーのリスクが高くなります。

通常のC#開発では、必要がない限りdynamicの多用は避けたほうがよいです。

9-3. object型とジェネリクスの違い

ジェネリクスは、型をパラメータとして扱う仕組みです。

たとえば、List<T>Tには具体的な型を指定できます。

C#
List<string> names = new List<string>();
names.Add("Alice");
names.Add("Bob");

このリストにはstring型の値だけを入れられます。

C#
// コンパイルエラー
// names.Add(123);

一方、List<object>を使うと、さまざまな型を入れられます。

C#
List<object> values = new List<object>();

values.Add("Alice");
values.Add(123);
values.Add(true);

ただし、取り出すときにキャストや型判定が必要です。

C#
foreach (object value in values)
{
Console.WriteLine(value);
}

ジェネリクスを使えば、型安全性を保ちながら汎用的なコードを書けます。

C#
void Print<T>(T value)
{
Console.WriteLine(value);
}

このメソッドは、文字列でも数値でも受け取れます。

C#
Print<string>("Hello");
Print<int>(100);

object型は「何でも入る箱」です。一方、ジェネリクスは「型を決めたうえで汎用的に使える仕組み」です。

9-4. どれを使うべきか判断する基準

objectvardynamic、ジェネリクスの使い分けは、次のように考えると分かりやすいです。

型が明確で、単に型名の記述を省略したいだけならvarを使います。

C#
var message = "Hello";

本当にどんな型でも受け取りたい場合はobjectを使います。

C#
void Print(object value)
{
Console.WriteLine(value);
}

型を安全に保ちながら汎用的な処理を書きたい場合は、ジェネリクスを使います。

C#
void Print<T>(T value)
{
Console.WriteLine(value);
}

実行時にメンバーを解決したい特殊な事情がある場合は、dynamicを検討します。

C#
dynamic value = GetDynamicValue();

初心者のうちは、まず具体的な型、次にvar、汎用化したい場合はジェネリクス、どうしても必要な場合だけobjectdynamicを使う、という順番で考えると安全です。

10. 初心者がつまずきやすいobject型のエラーと解決策

10-1. InvalidCastExceptionが発生する原因

InvalidCastExceptionは、キャストできない型に変換しようとしたときに発生します。

C#
object value = "Hello";

int number = (int)value;

このコードでは、valueの中身はstring型です。それをint型にキャストしようとしているため、実行時にエラーになります。

解決策は、キャストする前に型を確認することです。

C#
object value = "Hello";

if (value is int number)
{
Console.WriteLine(number);
}
else
{
Console.WriteLine("int型ではありません");
}

文字列を数値に変換したい場合は、キャストではなくint.Parseint.TryParseを使います。

C#
object value = "123";

if (value is string text && int.TryParse(text, out int number))
{
Console.WriteLine(number);
}

object型からのキャストは、実際の中身の型と一致している必要があります。

10-2. NullReferenceExceptionが発生する原因

NullReferenceExceptionは、nullの値に対してメソッドやプロパティを呼び出そうとしたときに発生します。

C#
object value = null;

// NullReferenceException
Console.WriteLine(value.ToString());

object型にはnullを代入できます。そのため、メソッドを呼ぶ前にnullチェックを行う必要があります。

C#
object value = null;

if (value != null)
{
Console.WriteLine(value.ToString());
}
else
{
Console.WriteLine("valueはnullです");
}

C#では、null条件演算子を使うこともできます。

C#
object value = null;

Console.WriteLine(value?.ToString());

value?.ToString()は、valuenullでない場合だけToStringを呼び出します。nullの場合は、結果もnullになります。

10-3. object型のままメソッドやプロパティを呼べない理由

object型の変数では、元の型に固有のメソッドやプロパティをそのまま呼び出せません。

C#
object value = "Hello";

// コンパイルエラー
// Console.WriteLine(value.Length);

中身は文字列ですが、変数の型はobjectです。C#コンパイラは、valueobject型として扱います。

object型にはLengthプロパティが定義されていないため、コンパイルエラーになります。

文字列として扱いたい場合は、キャストやパターンマッチングを使います。

C#
object value = "Hello";

if (value is string text)
{
Console.WriteLine(text.Length);
}

このように、object型の変数を使う場合は、「中身の実際の型」と「変数として見えている型」を分けて考えることが大切です。

10-4. 型判定とキャストで安全に処理する方法

object型を安全に扱うには、型判定とキャストを組み合わせます。

おすすめは、is演算子によるパターンマッチングです。

C#
void PrintLength(object value)
{
if (value is string text)
{
Console.WriteLine(text.Length);
}
else
{
Console.WriteLine("文字列ではありません");
}
}

複数の型を扱う場合は、switchを使うと読みやすくなります。

C#
void PrintValue(object value)
{
switch (value)
{
case null:
Console.WriteLine("nullです");
break;

case string text:
Console.WriteLine($"文字列: {text}");
break;

case int number:
Console.WriteLine($"整数: {number}");
break;

case bool flag:
Console.WriteLine($"真偽値: {flag}");
break;

default:
Console.WriteLine($"その他の型: {value.GetType()}");
break;
}
}

object型を扱うときは、いきなりキャストするのではなく、まず型を確認することが安全な書き方です。

11. object型を理解するための実践サンプル

11-1. object型の変数に複数の型を代入する

object型の変数には、異なる型の値を順番に代入できます。

C#
object value;

value = "Hello";
Console.WriteLine(value);

value = 100;
Console.WriteLine(value);

value = true;
Console.WriteLine(value);

value = DateTime.Now;
Console.WriteLine(value);

このように、同じobject型の変数に文字列、数値、真偽値、日付を代入できます。

ただし、それぞれの型に固有の処理をしたい場合は、型判定が必要です。

C#
object value = "Hello";

if (value is string text)
{
Console.WriteLine(text.ToUpper());
}

11-2. object型の値を型ごとに分岐して処理する

実践的な例として、object型の値を受け取り、型ごとに処理を分けるメソッドを作ってみます。

C#
void ProcessValue(object value)
{
switch (value)
{
case null:
Console.WriteLine("値はnullです");
break;

case string text:
Console.WriteLine($"文字列です。長さ: {text.Length}");
break;

case int number:
Console.WriteLine($"整数です。2倍: {number * 2}");
break;

case double number:
Console.WriteLine($"小数です。半分: {number / 2}");
break;

case bool flag:
Console.WriteLine($"真偽値です。値: {flag}");
break;

default:
Console.WriteLine($"その他の型です。型名: {value.GetType()}");
break;
}
}

使い方は次のとおりです。

C#
ProcessValue("Hello");
ProcessValue(100);
ProcessValue(3.14);
ProcessValue(true);
ProcessValue(DateTime.Now);
ProcessValue(null);

object型を使うと、このようにさまざまな型を1つのメソッドで受け取れます。

ただし、型ごとの分岐が増えすぎる場合は、設計を見直すサインです。

11-3. object型のリストを扱う

List<object>を使うと、異なる型の値を同じリストに入れられます。

C#
List<object> values = new List<object>();

values.Add("Apple");
values.Add(100);
values.Add(true);
values.Add(DateTime.Now);

取り出すときは、型ごとに処理を分けます。

C#
foreach (object value in values)
{
switch (value)
{
case string text:
Console.WriteLine($"文字列: {text}");
break;

case int number:
Console.WriteLine($"整数: {number}");
break;

case bool flag:
Console.WriteLine($"真偽値: {flag}");
break;

case DateTime date:
Console.WriteLine($"日付: {date:yyyy/MM/dd}");
break;

default:
Console.WriteLine($"その他: {value}");
break;
}
}

List<object>は柔軟ですが、型のチェックやキャストが必要になります。

もし同じ型の値だけを扱うなら、List<string>List<int>のように具体的な型を使うほうが適しています。

11-4. ジェネリクスに置き換える例

object型を使ったコードは、ジェネリクスに置き換えられることがあります。

たとえば、次のようなメソッドを考えます。

C#
void PrintObject(object value)
{
Console.WriteLine(value);
}

このメソッドはどんな型でも受け取れますが、型情報はobjectになります。

ジェネリクスを使うと、型を保ったまま汎用的に書けます。

C#
void Print<T>(T value)
{
Console.WriteLine(value);
}

使い方は次のとおりです。

C#
Print<string>("Hello");
Print<int>(100);
Print<bool>(true);

多くの場合、単に「どんな型でも受け取りたい」だけなら、object型よりもジェネリクスのほうが安全で柔軟です。

リストの場合も同じです。

C#
List<object> objectList = new List<object>();
objectList.Add("Hello");
objectList.Add(100);

このようにすると異なる型を混在できますが、取り出すときに型判定が必要です。

一方、同じ型だけを扱うなら、ジェネリクスを使います。

C#
List<string> stringList = new List<string>();
stringList.Add("Hello");
stringList.Add("World");

型が明確な場合は、object型ではなくジェネリクスを使うのが基本です。

12. C#のobject型に関するよくある質問

12-1. C#ではすべての型がobject型を継承している?

はい。C#では、すべての型が最終的にobject型を基にしています。

クラスはもちろん、構造体や列挙型などの値型も、最終的にはSystem.Objectの機能を持っています。

C#
int number = 10;
Console.WriteLine(number.ToString());

int型でもToStringメソッドを使えるのは、object型に由来する基本的なメソッドを利用できるためです。

ただし、値型をobject型として扱う場合には、ボックス化が発生する点に注意が必要です。

C#
int number = 10;
object obj = number;

12-2. object型にnullは入れられる?

はい。object型にはnullを代入できます。

C#
object value = null;

ただし、nullの状態でメソッドを呼び出すとNullReferenceExceptionが発生します。

C#
object value = null;

// 実行時エラー
// Console.WriteLine(value.ToString());

安全に扱うには、nullチェックを行います。

C#
if (value != null)
{
Console.WriteLine(value.ToString());
}

または、null条件演算子を使います。

C#
Console.WriteLine(value?.ToString());

12-3. object型とObject型は違う?

C#において、objectSystem.Objectは同じものです。

C#
object value1 = "Hello";
System.Object value2 = "Hello";

objectはC#のキーワードで、System.Objectの別名です。

通常のC#コードでは、objectと書くことが多いです。

C#
object value = 123;

一方、ドキュメントや型情報を厳密に表す文脈では、System.Objectと書かれることもあります。

12-4. object型は初心者でも使うべき?

初心者でもobject型を理解しておくことは大切です。

なぜなら、C#の型システムやクラス、インスタンス、継承、キャスト、ボックス化を理解するうえで、object型は基礎になるからです。

ただし、初心者が実際のコードでobject型を多用するのはおすすめしません。

型が分かっている場合は、具体的な型を使うほうが安全です。

C#
string name = "佐藤";
int age = 25;

何でも入るからといって、次のように書く必要はありません。

C#
object name = "佐藤";
object age = 25;

学習として理解することは重要ですが、実務的なコードでは、必要な場面に絞って使うのがよいです。

12-5. object型ではなくジェネリクスを使うべき場面は?

同じ型のデータを安全に扱いたい場合や、型を保ったまま汎用的な処理を書きたい場合は、object型よりもジェネリクスを使うべきです。

たとえば、文字列のリストならList<object>ではなくList<string>を使います。

C#
List<string> names = new List<string>();

names.Add("佐藤");
names.Add("鈴木");

数値のリストならList<int>を使います。

C#
List<int> numbers = new List<int>();

numbers.Add(10);
numbers.Add(20);

ジェネリクスを使うと、間違った型を追加しようとした時点でコンパイルエラーになります。

C#
List<int> numbers = new List<int>();

// コンパイルエラー
// numbers.Add("Hello");

object型では、実行時までミスに気づきにくい場合があります。

C#
List<object> values = new List<object>();

values.Add(10);
values.Add("Hello");

型安全性、可読性、パフォーマンスを考えると、型が決まっている場面ではジェネリクスを使うのが基本です。

まとめ

C#のobject型は、すべての型の基底型です。objectSystem.Objectの別名であり、文字列、数値、真偽値、自作クラスのインスタンスなど、さまざまな値を代入できます。

C#
object text = "Hello";
object number = 100;
object flag = true;
object person = new Person();

ただし、object型の変数として扱っている間は、元の型に固有のメソッドやプロパティをそのまま呼び出すことはできません。

C#
object value = "Hello";

// コンパイルエラー
// Console.WriteLine(value.Length);

元の型として扱うには、キャストや型判定が必要です。

C#
if (value is string text)
{
Console.WriteLine(text.Length);
}

また、値型をobject型に代入するとボックス化が発生し、object型から値型に戻すとボックス化解除が発生します。大量の値型を扱う場合は、パフォーマンスにも注意が必要です。

object型は、どんな型でも受け取りたい場合や、一時的に複数の型を扱いたい場合、古いAPIやライブラリを使う場合に役立ちます。

一方で、型安全性が下がる、キャストミスで例外が発生する、コードの可読性が下がる、ボックス化によるコストが発生するなどの注意点もあります。

型が明確な場合は、object型ではなく具体的な型を使いましょう。

C#
string name = "佐藤";
int age = 25;

汎用的な処理を書きたい場合は、object型よりもジェネリクスを検討しましょう。

C#
void Print<T>(T value)
{
Console.WriteLine(value);
}

C#のobject型を理解すると、クラス、インスタンス、継承、キャスト、ボックス化、ジェネリクスといった重要な概念も理解しやすくなります。

まずは「object型はすべての型の基底型」「何でも入れられるが、使うときは型判定やキャストが必要」「多用せず、必要な場面に絞って使う」という3点を押さえておきましょう。