型変換の基本概念
異なる型の変数間で値を代入する場合、ソース変数の型をターゲット変数の型に変換する必要があります。変換とは、ある型の値を受け取り、それを別の型の等価な値として使用するプロセスです。変換後の値はソース値と同じデータを参照しますが、その型はターゲット型となります。
short sourceValue = 5;
sbyte targetValue = 10;
targetValue = (sbyte)sourceValue; // キャスト式による明示的変換
暗黙的変換
データ損失や精度の低下を伴わない型変換は、コンパイラによって自動的に行われます。これを暗黙的変換と呼びます。例えば、8ビットの値を16ビットの変数に代入する場合などが該当します。
符号なし型の変換
より小さな符号なし型から大きな符号なし型へ変換する際、ターゲット型の上位ビットはゼロで埋められます。これは「ゼロ拡張」と呼ばれます。
符号付き型の変換
符号付き型をより大きな型に変換する場合、追加の上位ビットはソース値の符号ビットで埋められます。これにより、変換後の値の正負と大きさが正しく維持されます。これを「符号拡張」と呼びます。
明示的変換とキャスト
暗黙的変換が許可されていない場合、データ損失のリスクを承知の上で明示的に変換を行う必要があります。これをキャストと呼び、キャスト式を使用して記述します。
sbyte result = (sbyte)sourceValue;
キャストを使用すると、データ損失が発生する可能性があります。
// データが収まる場合
ushort largeVal = 10;
byte smallVal = (byte)largeVal; // 結果: 10
// データが収まらない場合
largeVal = 1365;
smallVal = (byte)largeVal; // 結果: 85 (オーバーフロー)
変換の種類
標準的な変換に加え、ユーザー定義の暗黙的・明示的変換を作成できます。また、ボックス化と呼ばれる特別な変換により、任意の値型を object 型や System.ValueType 型に変換できます。ボックス化解除は、ボックス化された値を元の型に戻す操作です。
数値型の変換
数値型は他の数値型へ変換可能ですが、変換の種類(暗黙的または明示的)は型の組み合わせによって異なります。
暗黙的な数値変換
変換元の型から変換先の型へ、データ損失なしに暗黙的に変換できるパスが存在する場合、暗黙的変換が可能です。存在しない場合は明示的変換が必要です。
オーバーフローチェックコンテキスト
checked 演算子およびステートメントを使用すると、整数型の変換時にオーバーフローが発生したかどうかを検出できます。
checked: オーバーフロー時にOverflowExceptionがスローされます。unchecked: オーバーフローが発生しても例外はスローされず、結果は切り捨てられます。
checked/unchecked 演算子
式に対してオーバーフローチェックを適用します。
ushort val = 2000;
byte uncheckedResult = unchecked((byte)val); // 結果: 208 (データ損失)
byte checkedResult = checked((byte)val); // OverflowException がスローされる
checked/unchecked ステートメント
コードブロック内のすべての変換に対してオーバーフローチェックを適用します。ネスト可能です。
checked
{
unchecked
{
// ここは unchecked コンテキスト
}
// ここは checked コンテキスト
}
明示的な数値変換
整数型から整数型
checked コンテキストではデータ損失時に例外がスローされます。unchecked コンテキストでは、上位ビットが切り捨てられます。
浮動小数点数から整数型
小数部分は切り捨てられます。checked コンテキストで結果がターゲット型の範囲外の場合、例外がスローされます。
float fVal = 10.9f;
int iVal = (int)fVal; // 結果: 10
decimal から整数型
結果がターゲット型の範囲外の場合、checked かどうかにかかわらず例外がスローされます。
double から float
double 値は最も近い float 値に丸められます。値が小さすぎる場合はゼロ、大きすぎる場合は正または負の無限大になります。
float/double から decimal
値が小さすぎる場合は0になり、大きすぎる場合は例外がスローされます。
decimal から float/double
この変換は常に成功しますが、精度が失われる可能性があります。
参照型の変換
参照型オブジェクトは、参照とデータの2つの部分で構成されます。参照変換は、ヒープ上の同じ場所を指す参照を返しますが、その参照を別の型としてマークします。
暗黙的な参照変換
- すべての参照型は
object型に暗黙的に変換できます。 - 任意のインターフェースは、そのインターフェースが継承しているインターフェースに暗黙的に変換できます。
- クラスは、継承チェーン内の任意の基底クラスや実装しているインターフェースに暗黙的に変換できます。
明示的な参照変換
基底クラスから派生クラスへの変換など、ダウンキャストは明示的な参照変換です。不適切な変換はコンパイラによって許可されますが、実行時に例外がスローされます。
BaseClass baseObj = new BaseClass();
DerivedClass derivedObj = (DerivedClass)baseObj; // 実行時エラー
有効な明示的参照変換
実行時に成功する明示的変換には以下のケースがあります。
- 変換が不要な場合(アップキャストなど)。
- ソース参照が
nullの場合。 - ソース参照が指す実際のデータが、ターゲット型に安全に変換できる場合。
DerivedClass d = new DerivedClass();
BaseClass b = d; // 暗黙的変換
DerivedClass d2 = (DerivedClass)b; // 有効な明示的変換
ボックス化
値型はデフォルトでヒープ上にオブジェクトコンポーネントを持ちませんが、ボックス化によって値型の値をヒープ上の完全な参照型オブジェクトとして扱うことができます。ボックス化は暗黙的な変換です。
int number = 12;
object boxedObj = number; // ボックス化
ボックス化はコピーを作成します。ボックス化後、元の値型とボックス化されたコピーは独立して操作できます。
ボックス化解除
ボックス化解除は明示的な変換です。ボックス化されたオブジェクトがターゲットの値型と互換性があるか確認し、その値をコピーします。
int original = 10;
object boxed = original;
int unboxed = (int)boxed; // ボックス化解除
ユーザー定義の変換
クラスまたは構造体に対して、カスタムの暗黙的または明示的変換を定義できます。
構文
// 暗黙的変換
public static implicit operator TargetType(SourceType source)
{
return new TargetType(source);
}
// 明示的変換
public static explicit operator TargetType(SourceType source)
{
return new TargetType(source);
}
制約
- クラスまたは構造体に対してのみ定義可能です。
- ソース型とターゲット型は異なり、継承関係になく、どちらもインターフェースや
object型であってはなりません。 - 変換演算子はソース型またはターゲット型のメンバーである必要があります。
複数ステップの変換
ユーザー定義変換は、標準変換の前後に最大1回ずつ組み合わせて使用できます。変換チェーンには最大1つのユーザー定義変換しか含めることができません。
is 演算子
is 演算子は、変換が成功するかどうかを確認するために使用します。参照変換、ボックス化、ボックス化解除に対して使用できますが、ユーザー定義変換には使用できません。
Employee emp = new Employee();
if (emp is Person)
{
Person p = (Person)emp;
// 変換成功時の処理
}
as 演算子
as 演算子はキャストに似ていますが、変換に失敗した場合に例外をスローせず null を返します。ターゲット型は参照型である必要があります。ユーザー定義変換には使用できません。
Employee emp = new Employee();
Person p = emp as Person;
if (p != null)
{
// 変換成功時の処理
}