C#のobject型とは?使い方・キャスト・型変換の注意点を初心者向けに徹底解説
はじめに
C#でプログラミングを学んでいると、object型という特別な型を目にすることがあります。
int、string、boolなどは役割が分かりやすい一方で、object型は「何でも入れられる型」と説明されることが多く、初心者にとっては少し分かりにくい型です。
たとえば、次のように数値も文字列もobject型の変数に代入できます。
C#object value1 = 100;
object value2 = "Hello";
object value3 = true;
一見便利に見えますが、object型を使うときにはキャスト、型変換、ボックス化、アンボックス化などの注意点があります。使い方を誤ると、実行時エラーが発生したり、コードの可読性が下がったりする原因になります。
この記事では、C#のobject型とは何か、基本的な使い方、キャストや型変換の注意点、var・dynamic・ジェネリックとの違いまで、初心者向けに分かりやすく解説します。
1. C#のobject型とは?まず押さえたい基本
1-1. object型はすべての型の基底クラス
C#のobject型は、すべての型の基底となる型です。
正確には、objectはSystem.Objectの別名です。C#では次の2つは同じ意味になります。
C#object value = "Hello";
System.Object value2 = "World";
C#のすべての型は、最終的にSystem.Objectを継承しています。そのため、intやdoubleのような値型、stringや配列、クラスのような参照型も、object型として扱うことができます。
C#object number = 10;
object text = "C#";
object flag = true;
このように、object型はC#におけるもっとも基本的で広い型だと考えると理解しやすいです。
1-2. 値型・参照型のどちらもobject型に代入できる
C#の型は大きく分けると、値型と参照型に分類されます。
値型には、int、double、bool、char、structなどがあります。参照型には、string、配列、クラス、List<T>などがあります。
object型には、このどちらも代入できます。
C#object number = 123; // int型
object price = 980.5; // double型
object name = "田中"; // string型
object isActive = true; // bool型
ただし、値型をobject型に代入するときには「ボックス化」という処理が発生します。ボックス化については後ほど詳しく解説します。
1-3. object型を使うと何ができるのか
object型を使うと、異なる型の値を同じ変数や配列、リストで扱うことができます。
たとえば、次のように複数の型を1つの配列にまとめられます。
C#object[] values = { 1, "Hello", true, 3.14 };
foreach (object value in values)
{
Console.WriteLine(value);
}
実行結果は次のようになります。
1
Hello
True
3.14
通常、int[]には整数しか入れられませんし、string[]には文字列しか入れられません。しかし、object[]であればさまざまな型の値をまとめて扱えます。
また、メソッドの引数をobject型にすると、さまざまな型の値を受け取れるようになります。
C#void PrintValue(object value)
{
Console.WriteLine(value);
}
PrintValue(100);
PrintValue("Hello");
PrintValue(true);
このように柔軟な処理を書ける点が、object型の特徴です。
1-4. 初心者が混乱しやすい「型が分からなくなる」という特徴
object型は便利ですが、初心者が混乱しやすいポイントがあります。それは、object型に代入した時点で、変数の見た目から元の型が分かりにくくなることです。
C#object value = 100;
このコードでは、実際に入っている値はint型の100です。しかし、変数valueの型はobjectです。
そのため、次のような計算はそのままではできません。
C#object value = 100;
// エラー
// int result = value + 10;
valueはobject型として扱われているため、C#コンパイラは「この値が本当に数値なのか」を判断できません。計算するには、元の型にキャストする必要があります。
C#object value = 100;
int number = (int)value;
int result = number + 10;
Console.WriteLine(result); // 110
つまり、object型は何でも入れられる反面、取り出して使うときには型を意識する必要があります。
2. C#におけるobject型の使い方
2-1. object型の変数を宣言する基本構文
object型の変数は、次のように宣言します。
C#object 変数名;
値を代入する場合は、次のように書きます。
C#object value = 123;
あとから別の型の値を代入することもできます。
C#object value = 123;
value = "Hello";
value = true;
このコードは問題なく動作します。object型はすべての型を受け取れるため、最初に整数を代入し、その後に文字列や真偽値を代入することも可能です。
ただし、何でも代入できるからといって、むやみに使うのはおすすめできません。プログラムの途中で中身の型が変わると、コードを読む人が理解しにくくなるためです。
2-2. 文字列・数値・真偽値をobject型に代入する例
次のコードでは、文字列、数値、真偽値をそれぞれobject型に代入しています。
C#object name = "佐藤";
object age = 25;
object isMember = true;
Console.WriteLine(name);
Console.WriteLine(age);
Console.WriteLine(isMember);
実行結果は次のようになります。
佐藤
25
True
Console.WriteLineはobject型を受け取ることができるため、このように出力できます。
ただし、ageを数値として計算したい場合は注意が必要です。
C#object age = 25;
// エラー
// int nextAge = age + 1;
この場合は、次のようにint型へキャストします。
C#object age = 25;
int nextAge = (int)age + 1;
Console.WriteLine(nextAge); // 26
2-3. object型の配列やリストで複数の型を扱う例
object型の配列を使うと、異なる型の値を1つの配列に入れられます。
C#object[] data = new object[4];
data[0] = 1;
data[1] = "Apple";
data[2] = true;
data[3] = 120.5;
foreach (object item in data)
{
Console.WriteLine(item);
}
List<object>を使うこともできます。
C#List<object> values = new List<object>();
values.Add(100);
values.Add("C#");
values.Add(false);
values.Add(99.9);
foreach (object value in values)
{
Console.WriteLine(value);
}
このように、object型を使うと複数の型をまとめて扱うことができます。
ただし、実務ではList<object>を多用することはあまりおすすめされません。なぜなら、リストの中にどの型が入っているか分かりにくくなり、キャストミスや実行時エラーの原因になるからです。
型が決まっている場合は、次のように具体的な型を使うほうが安全です。
C#List<int> scores = new List<int>();
scores.Add(80);
scores.Add(90);
scores.Add(75);
2-4. メソッドの引数や戻り値にobject型を使うケース
object型は、メソッドの引数にも使えます。
C#void Print(object value)
{
Console.WriteLine(value);
}
Print(123);
Print("Hello");
Print(true);
このように書くと、さまざまな型の値を同じメソッドで受け取れます。
戻り値にobject型を使うこともできます。
C#object GetValue(int type)
{
if (type == 1)
{
return 100;
}
else if (type == 2)
{
return "Hello";
}
else
{
return false;
}
}
ただし、戻り値がobject型の場合、呼び出し側は戻ってきた値の型を確認する必要があります。
C#object result = GetValue(1);
if (result is int number)
{
Console.WriteLine(number + 10);
}
戻り値の型が複数になる設計は、便利な反面、コードが複雑になりやすいです。可能であれば、戻り値の型は具体的に決めたほうがよいでしょう。
2-5. object型を使うべき場面と避けるべき場面
object型を使うべき場面は、主に次のようなケースです。
異なる型の値を一時的にまとめて扱いたい場合や、共通的なログ出力、デバッグ表示、汎用的な処理を作りたい場合にはobject型が役立ちます。
C#void Log(object message)
{
Console.WriteLine($"LOG: {message}");
}
Log("処理開始");
Log(123);
Log(true);
一方で、次のような場面ではobject型を避けたほうがよいです。
たとえば、リストに入る値の型が最初から分かっている場合は、List<object>ではなくList<int>やList<string>を使うべきです。
C#// 避けたい例
List<object> names = new List<object>();
names.Add("田中");
names.Add("佐藤");
// 推奨
List<string> names2 = new List<string>();
names2.Add("田中");
names2.Add("佐藤");
object型は便利ですが、型安全性が下がるため、必要な場面に限定して使うことが大切です。
3. object型とボックス化・アンボックス化
3-1. ボックス化とは何か
ボックス化とは、intやboolなどの値型をobject型として扱うときに発生する処理です。
値型は通常、値そのものを保持します。一方、object型は参照型です。そのため、値型をobject型に代入すると、値がオブジェクトとして包まれます。この処理をボックス化と呼びます。
C#int number = 10;
object obj = number; // ボックス化
このコードでは、int型のnumberがobject型のobjに代入されています。このとき、numberの値がオブジェクトとして扱えるように変換されます。
3-2. intなどの値型をobject型に代入すると何が起きるか
次のコードを見てください。
C#int number = 10;
object obj = number;
number = 20;
Console.WriteLine(number);
Console.WriteLine(obj);
実行結果は次のようになります。
20
10
objには、numberを代入した時点の値である10が入っています。その後でnumberを20に変更しても、objの中身は変わりません。
これは、ボックス化によって値がコピーされるためです。
値型をobject型に代入すると、元の変数そのものが入るのではなく、値がコピーされてobject型として保持されると考えると理解しやすいです。
3-3. アンボックス化とは何か
アンボックス化とは、object型として保持されている値型の値を、元の値型として取り出す処理です。
C#object obj = 10; // ボックス化
int number = (int)obj; // アンボックス化
Console.WriteLine(number); // 10
object型に入っている値をint型として使うには、明示的なキャストが必要です。
C#object obj = 100;
int number = (int)obj;
Console.WriteLine(number + 50); // 150
このように、object型に入れた値型を元に戻す処理がアンボックス化です。
3-4. アンボックス化で例外が発生するケース
アンボックス化では、元の型と違う型にキャストすると例外が発生します。
C#object obj = 10;
// InvalidCastExceptionが発生
double number = (double)obj;
このコードは、10という値が入っているため一見doubleに変換できそうに見えます。しかし、objの中身はint型としてボックス化されています。そのため、直接double型へアンボックス化することはできません。
正しくは、まずintとして取り出してからdoubleへ変換します。
C#object obj = 10;
int intValue = (int)obj;
double doubleValue = intValue;
Console.WriteLine(doubleValue);
または、Convert.ToDoubleを使う方法もあります。
C#object obj = 10;
double doubleValue = Convert.ToDouble(obj);
Console.WriteLine(doubleValue);
アンボックス化では、「中身の値が変換できそうか」ではなく、「実際にボックス化された型と一致しているか」が重要です。
3-5. パフォーマンス面で注意すべきポイント
ボックス化とアンボックス化は、通常の値型の代入よりもコストがかかります。
たとえば、次のように大量の数値をList<object>に入れると、各数値でボックス化が発生します。
C#List<object> values = new List<object>();
for (int i = 0; i < 100000; i++)
{
values.Add(i); // intからobjectへのボックス化
}
一方、List<int>を使えばボックス化は発生しません。
C#List<int> values = new List<int>();
for (int i = 0; i < 100000; i++)
{
values.Add(i);
}
通常の小さなプログラムでは大きな問題にならないことも多いですが、大量データを扱う処理やパフォーマンスが重要な処理では注意が必要です。
型が決まっている場合は、object型ではなく具体的な型やジェネリックを使うのが基本です。
4. object型のキャスト方法
4-1. 明示的キャストの基本
object型に入っている値を元の型として使うには、キャストが必要です。
明示的キャストは、次のように書きます。
C#変換先の型 変数名 = (変換先の型)object型の値;
たとえば、object型に入っているintを取り出す場合は次のように書きます。
C#object obj = 123;
int number = (int)obj;
Console.WriteLine(number);
文字列の場合も同じです。
C#object obj = "Hello";
string text = (string)obj;
Console.WriteLine(text);
明示的キャストはシンプルですが、キャスト先の型を間違えると実行時エラーになります。
4-2. object型からint・stringなどへキャストする例
object型からint、string、boolへキャストする例を見てみましょう。
C#object obj1 = 100;
object obj2 = "C#";
object obj3 = true;
int number = (int)obj1;
string text = (string)obj2;
bool flag = (bool)obj3;
Console.WriteLine(number);
Console.WriteLine(text);
Console.WriteLine(flag);
実行結果は次のようになります。
100
C#
True
キャスト先の型が正しければ問題なく取り出せます。
ただし、次のようなコードはエラーになります。
C#object obj = "100";
// InvalidCastExceptionが発生
int number = (int)obj;
"100"は文字列であり、intではありません。文字列を数値に変換したい場合は、キャストではなく型変換を使う必要があります。
C#object obj = "100";
int number = int.Parse((string)obj);
Console.WriteLine(number);
または、Convert.ToInt32を使うこともできます。
C#object obj = "100";
int number = Convert.ToInt32(obj);
Console.WriteLine(number);
4-3. キャストに失敗すると発生するInvalidCastException
object型のキャストに失敗すると、InvalidCastExceptionが発生します。
C#object obj = "Hello";
int number = (int)obj; // InvalidCastException
このエラーは、実行時に「その型には変換できない」と判断されたときに発生します。
初心者が特に間違えやすいのは、見た目の値と実際の型を混同することです。
C#object obj = "123";
// これはエラー
// int number = (int)obj;
"123"は数値のように見えますが、型はstringです。そのため、intへ直接キャストすることはできません。
正しくは、文字列から数値への変換を行います。
C#object obj = "123";
if (int.TryParse(obj.ToString(), out int number))
{
Console.WriteLine(number + 10);
}
else
{
Console.WriteLine("数値に変換できません");
}
4-4. as演算子を使った安全なキャスト
as演算子を使うと、キャストに失敗した場合に例外を発生させず、nullを返すことができます。
C#object obj = "Hello";
string text = obj as string;
if (text != null)
{
Console.WriteLine(text);
}
else
{
Console.WriteLine("string型ではありません");
}
as演算子は、主に参照型へのキャストで使います。たとえば、stringや自作クラスへのキャストに向いています。
C#object obj = new Person { Name = "田中" };
Person person = obj as Person;
if (person != null)
{
Console.WriteLine(person.Name);
}
class Person
{
public string Name { get; set; }
}
ただし、as演算子は通常の値型であるintには使えません。
C#object obj = 10;
// エラー
// int number = obj as int;
intのような値型を安全に扱いたい場合は、後述するis演算子やパターンマッチングを使うのが一般的です。
4-5. is演算子で型を確認してからキャストする方法
is演算子を使うと、object型の中身が特定の型かどうかを確認できます。
C#object obj = 100;
if (obj is int)
{
int number = (int)obj;
Console.WriteLine(number + 10);
}
else
{
Console.WriteLine("int型ではありません");
}
このように、キャストする前に型を確認すれば、InvalidCastExceptionを防ぎやすくなります。
string型かどうかを確認する場合も同じです。
C#object obj = "Hello";
if (obj is string)
{
string text = (string)obj;
Console.WriteLine(text);
}
ただし、現在のC#では、次に紹介するパターンマッチングを使った書き方のほうが簡潔でおすすめです。
4-6. パターンマッチングを使った現代的な書き方
C#では、is演算子と同時に変数宣言を行うパターンマッチングが使えます。
C#object obj = 100;
if (obj is int number)
{
Console.WriteLine(number + 10);
}
else
{
Console.WriteLine("int型ではありません");
}
この書き方では、objがint型だった場合だけ、numberという変数として使えます。
文字列の場合も同じです。
C#object obj = "Hello";
if (obj is string text)
{
Console.WriteLine(text.ToUpper());
}
複数の型で処理を分ける場合は、switch文とパターンマッチングを組み合わせると便利です。
C#object value = 123;
switch (value)
{
case int number:
Console.WriteLine($"整数です: {number}");
break;
case string text:
Console.WriteLine($"文字列です: {text}");
break;
case bool flag:
Console.WriteLine($"真偽値です: {flag}");
break;
default:
Console.WriteLine("その他の型です");
break;
}
object型を扱うときは、明示的キャストよりも、is演算子やパターンマッチングを使ったほうが安全で読みやすいコードになります。
5. object型と型変換の注意点
5-1. キャストと型変換の違い
object型を扱うときは、キャストと型変換の違いを理解しておくことが重要です。
キャストは、実際の型を別の型として取り出す操作です。
C#object obj = 100;
int number = (int)obj;
この場合、objの中身はもともとint型なので、intとして取り出せます。
一方、型変換は、値の意味を別の型に変える操作です。
C#object obj = "100";
int number = Convert.ToInt32(obj);
この場合、objの中身はstring型です。しかし、文字列"100"を数値100に変換しています。
つまり、次のように考えると分かりやすいです。
キャストは「本来その型であるものを、その型として取り出す」操作です。型変換は「別の型の値を、目的の型の値に変える」操作です。
5-2. Convert.ToInt32やToStringを使う場面
Convert.ToInt32は、object型に入っている値を整数に変換したいときに使えます。
C#object obj1 = "123";
object obj2 = 456;
object obj3 = true;
Console.WriteLine(Convert.ToInt32(obj1)); // 123
Console.WriteLine(Convert.ToInt32(obj2)); // 456
Console.WriteLine(Convert.ToInt32(obj3)); // 1
trueは1、falseは0に変換されます。
ただし、変換できない値を渡すと例外が発生します。
C#object obj = "Hello";
// FormatExceptionが発生
// int number = Convert.ToInt32(obj);
文字列として扱いたい場合は、ToStringを使います。
C#object obj = 123;
string text = obj.ToString();
Console.WriteLine(text); // "123"
ただし、objがnullの場合、obj.ToString()はNullReferenceExceptionになります。
C#object obj = null;
// NullReferenceException
// string text = obj.ToString();
nullの可能性がある場合は、次のように書くと安全です。
C#object obj = null;
string text = obj?.ToString() ?? "";
Console.WriteLine(text);
5-3. nullを扱うときの注意点
object型は参照型なので、nullを代入できます。
C#object value = null;
nullは「何も参照していない状態」を表します。
object型を扱うときは、nullチェックを忘れないことが大切です。
C#object value = null;
if (value != null)
{
Console.WriteLine(value.ToString());
}
else
{
Console.WriteLine("値がありません");
}
ToStringやGetTypeなどのメソッドを呼び出す前に、nullでないことを確認しましょう。
C#object value = null;
// エラー
// Console.WriteLine(value.GetType());
安全に書くなら、次のようにします。
C#object value = null;
if (value == null)
{
Console.WriteLine("nullです");
}
else
{
Console.WriteLine(value.GetType().Name);
}
5-4. 文字列から数値へ変換するときの注意点
object型に文字列が入っていて、その文字列を数値に変換したい場合は、int.ParseやConvert.ToInt32を使えます。
C#object obj = "123";
int number = int.Parse((string)obj);
Console.WriteLine(number);
ただし、文字列が数値として正しくない場合は例外が発生します。
C#object obj = "abc";
// FormatExceptionが発生
// int number = int.Parse((string)obj);
安全に変換したい場合は、int.TryParseを使います。
C#object obj = "123";
if (int.TryParse(obj?.ToString(), out int number))
{
Console.WriteLine(number + 10);
}
else
{
Console.WriteLine("数値に変換できません");
}
TryParseは、変換に成功した場合はtrueを返し、変換結果をout引数に入れます。失敗した場合はfalseを返すため、例外を避けられます。
ユーザー入力や外部ファイル、データベースから取得した値を扱う場合は、ParseよりもTryParseを使うほうが安全です。
5-5. 型変換エラーを防ぐ実装例
object型の値を安全に整数へ変換するメソッドを作るなら、次のように書けます。
C#bool TryConvertToInt(object value, out int result)
{
result = 0;
if (value == null)
{
return false;
}
if (value is int number)
{
result = number;
return true;
}
return int.TryParse(value.ToString(), out result);
}
使い方は次のとおりです。
C#object value1 = 100;
object value2 = "200";
object value3 = "abc";
if (TryConvertToInt(value1, out int result1))
{
Console.WriteLine(result1);
}
if (TryConvertToInt(value2, out int result2))
{
Console.WriteLine(result2);
}
if (TryConvertToInt(value3, out int result3))
{
Console.WriteLine(result3);
}
else
{
Console.WriteLine("value3は変換できません");
}
このように、object型を扱うときは、いきなりキャストや変換を行うのではなく、nullチェック、型チェック、変換可否の確認を行うと安全です。
6. object型とvar・dynamic・ジェネリックの違い
6-1. object型とvarの違い
object型とvarは混同されやすいですが、まったく別のものです。
objectは実際の型です。
C#object value = "Hello";
この場合、変数valueの型はobjectです。そのため、string型のメソッドを直接呼び出すことはできません。
C#object value = "Hello";
// エラー
// Console.WriteLine(value.Length);
一方、varはコンパイラに型を推論させるための書き方です。
C#var value = "Hello";
Console.WriteLine(value.Length);
この場合、valueの型はstringです。varと書いていますが、実際にはコンパイラがstring型だと判断しています。
つまり、varは「型をあいまいにする機能」ではありません。型を書く手間を省いているだけで、コンパイル時には具体的な型が決まっています。
6-2. object型とdynamicの違い
dynamic型も、object型と混同されやすいキーワードです。
object型では、メソッドやプロパティの存在がコンパイル時にチェックされます。
C#object value = "Hello";
// コンパイルエラー
// Console.WriteLine(value.Length);
一方、dynamic型では、メソッドやプロパティのチェックが実行時に行われます。
C#dynamic value = "Hello";
Console.WriteLine(value.Length); // 5
このコードはコンパイルできます。実行時にvalueの中身がstringなので、Lengthプロパティを使えます。
ただし、存在しないメソッドを呼び出してもコンパイルエラーにはならず、実行時エラーになります。
C#dynamic value = "Hello";
// 実行時エラー
// value.NotExistsMethod();
dynamicは便利ですが、型安全性が大きく下がるため、初心者が多用するのはおすすめできません。
6-3. object型とジェネリックの違い
ジェネリックは、型をパラメーターとして扱う仕組みです。
たとえば、List<T>のTには任意の型を指定できます。
C#List<int> numbers = new List<int>();
numbers.Add(1);
numbers.Add(2);
List<string> names = new List<string>();
names.Add("田中");
names.Add("佐藤");
List<object>を使えば何でも入れられます。
C#List<object> values = new List<object>();
values.Add(1);
values.Add("Hello");
values.Add(true);
しかし、List<object>では中身の型がバラバラになるため、取り出すときに型チェックやキャストが必要です。
一方、List<int>やList<string>であれば、型が決まっているため安全です。
C#List<int> numbers = new List<int>();
numbers.Add(10);
int number = numbers[0];
Console.WriteLine(number + 5);
ジェネリックは、柔軟性と型安全性を両立できる仕組みです。型が決まっている処理では、object型よりもジェネリックを使うほうが適しています。
6-4. 型安全性の観点で比較する
object、var、dynamic、ジェネリックを型安全性の観点で比較すると、次のように考えられます。
varは型推論を使うだけなので、型安全性は高いです。コンパイル時に具体的な型が決まります。
ジェネリックも型安全性が高いです。List<int>には基本的にintしか入れられないため、型の間違いをコンパイル時に検出できます。
objectは何でも入れられるため柔軟ですが、取り出すときにキャストが必要です。キャストミスは実行時エラーにつながります。
dynamicは実行時までメンバーの存在確認が遅れるため、型安全性は低くなります。
初心者が安全なコードを書くなら、基本は具体的な型、次にvar、必要に応じてジェネリックを使うのがおすすめです。objectやdynamicは、必要性が明確な場面で使いましょう。
6-5. 初心者はどれを使えばよいか
初心者は、まず具体的な型を使うことを意識しましょう。
C#int age = 20;
string name = "田中";
bool isActive = true;
型が明らかで、右辺から簡単に分かる場合はvarを使っても問題ありません。
C#var age = 20;
var name = "田中";
var isActive = true;
コレクションを扱う場合は、List<object>ではなく、できるだけList<int>やList<string>のように具体的な型を指定しましょう。
C#List<string> names = new List<string>();
names.Add("田中");
names.Add("佐藤");
object型は、複数の型をまとめて扱う必要がある場合や、共通的な処理を書く場合に使います。
ただし、最初からobject型に頼りすぎると、型の理解が深まりにくくなります。C#は静的型付け言語なので、型を明確にすることが読みやすく安全なコードにつながります。
7. object型でよく使うメソッド
7-1. ToStringメソッド
ToStringメソッドは、オブジェクトを文字列として表現するためのメソッドです。
C#object value = 123;
string text = value.ToString();
Console.WriteLine(text);
実行結果は次のようになります。
123
ToStringは、ログ出力や画面表示などでよく使われます。
C#object value = DateTime.Now;
Console.WriteLine(value.ToString());
ただし、object型の変数がnullの場合、ToStringを呼び出すとNullReferenceExceptionが発生します。
C#object value = null;
// エラー
// string text = value.ToString();
安全に書く場合は、null条件演算子を使います。
C#object value = null;
string text = value?.ToString() ?? "null";
Console.WriteLine(text);
7-2. Equalsメソッド
Equalsメソッドは、2つのオブジェクトが等しいかどうかを判定するためのメソッドです。
C#object value1 = 100;
object value2 = 100;
Console.WriteLine(value1.Equals(value2)); // True
文字列でも使えます。
C#object text1 = "Hello";
object text2 = "Hello";
Console.WriteLine(text1.Equals(text2)); // True
ただし、value1がnullの場合はvalue1.Equals(value2)と書くとエラーになります。
C#object value1 = null;
object value2 = "Hello";
// エラー
// Console.WriteLine(value1.Equals(value2));
安全に比較したい場合は、静的メソッドのobject.Equalsを使うと便利です。
C#object value1 = null;
object value2 = "Hello";
Console.WriteLine(object.Equals(value1, value2)); // False
7-3. GetHashCodeメソッド
GetHashCodeメソッドは、オブジェクトのハッシュコードを取得するためのメソッドです。
C#object value = "Hello";
Console.WriteLine(value.GetHashCode());
ハッシュコードは、辞書型のDictionary<TKey, TValue>や集合型のHashSet<T>などで内部的に使われます。
通常のプログラミングで頻繁に直接呼び出すことは多くありませんが、Equalsと関係が深いメソッドです。
自作クラスで等価性を正しく扱いたい場合は、EqualsとGetHashCodeをセットでオーバーライドすることがあります。
C#class User
{
public int Id { get; set; }
public override bool Equals(object obj)
{
if (obj is User other)
{
return Id == other.Id;
}
return false;
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
}
初心者のうちは、「オブジェクト比較やコレクションの内部処理で使われるメソッド」と理解しておけば十分です。
7-4. GetTypeメソッド
GetTypeメソッドは、オブジェクトの実際の型を取得するためのメソッドです。
C#object value = 123;
Console.WriteLine(value.GetType());
実行結果は次のようになります。
System.Int32
文字列の場合は次のようになります。
C#object value = "Hello";
Console.WriteLine(value.GetType());
実行結果は次のようになります。
System.String
object型の中に実際にどの型の値が入っているかを確認したいときに便利です。
ただし、valueがnullの場合はGetTypeを呼び出せません。
C#object value = null;
// NullReferenceException
// Console.WriteLine(value.GetType());
nullの可能性がある場合は、先にチェックしましょう。
C#object value = null;
if (value != null)
{
Console.WriteLine(value.GetType());
}
else
{
Console.WriteLine("nullです");
}
7-5. 実務で使いやすい確認コード例
object型の中身を確認しながら処理するサンプルです。
C#void ShowInfo(object value)
{
if (value == null)
{
Console.WriteLine("値: null");
Console.WriteLine("型: なし");
return;
}
Console.WriteLine($"値: {value}");
Console.WriteLine($"型: {value.GetType().Name}");
}
ShowInfo(100);
ShowInfo("Hello");
ShowInfo(true);
ShowInfo(null);
実行結果の例です。
値: 100
型: Int32
値: Hello
型: String
値: True
型: Boolean
値: null
型: なし
このような確認用メソッドを作っておくと、object型に何が入っているかを調べるときに便利です。
8. object型を使うメリット・デメリット
8-1. object型を使うメリット
object型の最大のメリットは、さまざまな型の値を扱える柔軟性です。
C#void Print(object value)
{
Console.WriteLine(value);
}
Print(100);
Print("Hello");
Print(true);
このように、同じメソッドで複数の型を受け取れます。
また、異なる型を1つの配列やリストにまとめることもできます。
C#object[] values = { 1, "C#", true };
foreach (object value in values)
{
Console.WriteLine(value);
}
ログ出力、デバッグ、汎用的なデータ保持などでは、object型が役立つ場面があります。
8-2. object型を使うデメリット
object型にはデメリットもあります。
まず、型が分かりにくくなります。
C#object value = GetValue();
このコードだけでは、valueに何が入っているのか分かりません。intなのか、stringなのか、自作クラスなのかを確認する必要があります。
また、取り出すときにキャストが必要になります。
C#object value = 100;
int number = (int)value;
キャスト先を間違えると、実行時エラーになります。
C#object value = "Hello";
// InvalidCastException
// int number = (int)value;
さらに、値型を扱う場合はボックス化・アンボックス化によるパフォーマンスコストも発生します。
8-3. 可読性が下がる理由
object型を多用すると、コードの可読性が下がりやすくなります。
たとえば、次のコードを見ても、dataの中身が何なのか分かりにくいです。
C#object data = GetData();
一方、具体的な型で書かれていれば、コードの意図が明確になります。
C#User user = GetUser();
User型であれば、「ユーザー情報を取得している」とすぐに分かります。しかし、object型では中身を確認しなければ意味が分かりません。
読みやすいコードを書くためには、できるだけ具体的な型を使うことが大切です。
8-4. 型安全性が失われやすい理由
C#は静的型付け言語です。つまり、コンパイル時に型の間違いを検出しやすいという特徴があります。
しかし、object型を多用すると、このメリットが弱くなります。
C#object value = "123";
// 実行時まで間違いに気づきにくい
int number = (int)value;
このコードはコンパイルできる場合がありますが、実行するとInvalidCastExceptionが発生します。
具体的な型を使っていれば、型の不一致をより早い段階で発見できます。
C#string value = "123";
// コンパイル時に意図を確認しやすい
int number = int.Parse(value);
型安全性を保つためには、object型に頼りすぎず、必要な場面でだけ使うことが重要です。
8-5. 保守しやすいコードを書くための考え方
保守しやすいコードを書くには、「何の型を扱っているか」が分かりやすいことが大切です。
object型を使う場合でも、次のような工夫をすると保守しやすくなります。
まず、object型を使う範囲をできるだけ狭くします。
C#object value = GetValue();
if (value is int number)
{
Console.WriteLine(number + 10);
}
型が分かったあとは、具体的な型の変数に入れて処理します。
また、複数の型を扱う必要がある場合は、switchのパターンマッチングを使うと読みやすくなります。
C#void Process(object value)
{
switch (value)
{
case int number:
Console.WriteLine($"数値: {number}");
break;
case string text:
Console.WriteLine($"文字列: {text}");
break;
case bool flag:
Console.WriteLine($"真偽値: {flag}");
break;
case null:
Console.WriteLine("nullです");
break;
default:
Console.WriteLine("未対応の型です");
break;
}
}
object型を使うときは、「どの型が入る可能性があるのか」「取り出すときにどう確認するのか」を明確にしておくことが大切です。
9. object型の実践コード例
9-1. object型に複数の値を代入するサンプル
次のコードでは、1つのobject型変数に複数の型の値を順番に代入しています。
C#object value;
value = 100;
Console.WriteLine(value);
value = "Hello";
Console.WriteLine(value);
value = true;
Console.WriteLine(value);
value = 3.14;
Console.WriteLine(value);
実行結果は次のようになります。
100
Hello
True
3.14
object型の変数には、異なる型の値を代入できます。
ただし、このように同じ変数の中身の型が何度も変わるコードは、実務では読みづらくなることがあります。学習用のサンプルとしては分かりやすいですが、実際の開発では型を明確にすることを意識しましょう。
9-2. is演算子で型ごとに処理を分けるサンプル
is演算子を使うと、object型の中身に応じて処理を分けられます。
C#void PrintByType(object value)
{
if (value is int number)
{
Console.WriteLine($"整数です: {number}");
}
else if (value is string text)
{
Console.WriteLine($"文字列です: {text}");
}
else if (value is bool flag)
{
Console.WriteLine($"真偽値です: {flag}");
}
else if (value == null)
{
Console.WriteLine("nullです");
}
else
{
Console.WriteLine("その他の型です");
}
}
PrintByType(100);
PrintByType("C#");
PrintByType(true);
PrintByType(null);
この書き方なら、キャスト前に型を確認できるため安全です。
9-3. as演算子で安全にキャストするサンプル
as演算子は、参照型へのキャストを安全に行いたいときに便利です。
C#object value = "Hello";
string text = value as string;
if (text != null)
{
Console.WriteLine(text.ToUpper());
}
else
{
Console.WriteLine("string型ではありません");
}
自作クラスでも使えます。
C#class User
{
public string Name { get; set; }
}
object value = new User { Name = "田中" };
User user = value as User;
if (user != null)
{
Console.WriteLine(user.Name);
}
else
{
Console.WriteLine("User型ではありません");
}
as演算子は、キャストに失敗しても例外を出さずにnullを返すため、nullチェックと組み合わせて使います。
9-4. object型のリストを扱うサンプル
List<object>を使うと、異なる型の値を1つのリストに入れられます。
C#List<object> values = new List<object>
{
100,
"Apple",
true,
3.14
};
foreach (object value in values)
{
switch (value)
{
case int number:
Console.WriteLine($"整数: {number}");
break;
case string text:
Console.WriteLine($"文字列: {text}");
break;
case bool flag:
Console.WriteLine($"真偽値: {flag}");
break;
case double d:
Console.WriteLine($"小数: {d}");
break;
default:
Console.WriteLine("その他");
break;
}
}
実行結果は次のようになります。
整数: 100
文字列: Apple
真偽値: True
小数: 3.14
このように、object型のリストを扱うときは、型ごとに処理を分ける必要があります。
9-5. よくあるエラーと修正例
よくあるエラーの1つは、文字列を数値に直接キャストしようとすることです。
C#object value = "123";
// エラー
// int number = (int)value;
この場合、valueの中身はstring型なので、intへ直接キャストできません。
修正例は次のとおりです。
C#object value = "123";
if (int.TryParse(value.ToString(), out int number))
{
Console.WriteLine(number + 10);
}
else
{
Console.WriteLine("数値に変換できません");
}
もう1つのよくあるエラーは、nullに対してToStringやGetTypeを呼び出すことです。
C#object value = null;
// エラー
// Console.WriteLine(value.ToString());
修正例は次のとおりです。
C#object value = null;
string text = value?.ToString() ?? "値がありません";
Console.WriteLine(text);
object型を扱うときは、「中身の型は何か」「nullではないか」「キャストではなく型変換が必要ではないか」を確認することが重要です。
10. object型に関するよくある質問
10-1. C#のobject型は何のためにある?
C#のobject型は、すべての型を共通して扱うためにあります。
int、string、bool、自作クラスなど、さまざまな型の値をobject型として扱えます。
たとえば、汎用的なログ出力メソッドを作る場合に便利です。
C#void Log(object value)
{
Console.WriteLine(value);
}
ただし、何でもobject型にすればよいわけではありません。型が決まっている場合は、具体的な型を使うほうが安全で読みやすいです。
10-2. object型には何でも入れられる?
基本的に、C#のほとんどの値はobject型に代入できます。
C#object a = 10;
object b = "Hello";
object c = true;
object d = new List<int>();
値型も参照型もobject型に代入できます。
ただし、値型をobject型に代入するとボックス化が発生します。また、object型から元の型として取り出すときには、キャストや型チェックが必要です。
10-3. object型とstring型の違いは?
object型はすべての型を扱える基底型です。一方、string型は文字列を扱うための型です。
C#object value = "Hello";
string text = "Hello";
どちらにも文字列を代入できますが、使えるメンバーが異なります。
C#string text = "Hello";
Console.WriteLine(text.Length); // OK
object型のままだと、string専用のLengthプロパティは直接使えません。
C#object value = "Hello";
// エラー
// Console.WriteLine(value.Length);
使いたい場合は、string型にキャストします。
C#object value = "Hello";
string text = (string)value;
Console.WriteLine(text.Length);
文字列として扱うことが分かっているなら、最初からstring型を使うほうがよいです。
10-4. object型とdynamic型はどちらを使うべき?
基本的には、dynamicよりもobject、さらに可能であれば具体的な型を使うことをおすすめします。
object型は、コンパイル時に使えるメソッドやプロパティがチェックされます。そのため、キャストは必要ですが、型の扱いが比較的明確です。
一方、dynamic型は実行時にメソッドやプロパティが解決されます。便利な反面、存在しないメソッドを呼び出してもコンパイル時には分からず、実行時エラーになります。
C#dynamic value = "Hello";
// 実行時エラーになる可能性がある
// value.NotExistsMethod();
初心者は、dynamicを安易に使わず、まずは具体的な型、var、ジェネリック、必要に応じてobject型を使うのがよいでしょう。
10-5. object型を使うと遅くなる?
object型を使ったからといって、すべての処理が必ず大きく遅くなるわけではありません。
しかし、intやboolなどの値型をobject型に代入すると、ボックス化が発生します。また、object型から値型に戻すときにはアンボックス化が発生します。
C#object value = 100; // ボックス化
int number = (int)value; // アンボックス化
少量の処理では問題にならないことも多いですが、大量のデータを扱う場合や高速な処理が求められる場合は、パフォーマンスに影響する可能性があります。
型が決まっているなら、object型ではなく具体的な型やジェネリックを使いましょう。
10-6. object型は初心者でも使ってよい?
初心者でもobject型を使って問題ありません。ただし、使いどころを理解することが大切です。
学習目的で、object型にさまざまな値を代入して動きを確認するのはよい練習になります。
C#object value = 100;
Console.WriteLine(value.GetType());
value = "Hello";
Console.WriteLine(value.GetType());
しかし、実際のコードでは、何でもobject型にするのは避けましょう。
C#// あまりよくない例
object name = "田中";
object age = 20;
// よい例
string name2 = "田中";
int age2 = 20;
初心者のうちは、まずint、string、boolなどの具体的な型をしっかり使い分けることが大切です。そのうえで、複数の型を共通して扱いたい場面でobject型を使うとよいでしょう。
まとめ
C#のobject型は、すべての型の基底となる特別な型です。int、string、bool、自作クラスなど、さまざまな型の値をobject型として扱うことができます。
object型を使うと、異なる型の値を同じ変数、配列、リスト、メソッドの引数などで扱えるため便利です。しかし、その一方で、取り出すときにはキャストや型チェックが必要になります。
特に、値型をobject型に代入するとボックス化が発生し、元の値型として取り出すときにはアンボックス化が発生します。キャスト先の型を間違えるとInvalidCastExceptionが発生するため注意が必要です。
安全にobject型を扱うには、is演算子やパターンマッチングを使って型を確認してから処理するのがおすすめです。
C#object value = 100;
if (value is int number)
{
Console.WriteLine(number + 10);
}
また、キャストと型変換の違いも重要です。object型に入っている値が文字列"123"の場合、intへ直接キャストするのではなく、int.Parse、int.TryParse、Convert.ToInt32などを使って変換します。
object型は便利ですが、多用すると可読性や型安全性が下がります。型が分かっている場合は、int、string、List<int>、List<string>など、具体的な型やジェネリックを使うのが基本です。
C#のobject型は、「何でも入れられる便利な型」であると同時に、「使い方を間違えるとエラーや読みにくいコードの原因になる型」でもあります。基本的な仕組み、キャスト、型変換、ボックス化・アンボックス化を理解して、必要な場面で適切に使いましょう。

