C#のキャストとは?明示的・暗黙的変換、as/isの違いと使い方を初心者向けに解説
C#のキャストとは?明示的・暗黙的変換、as/isの違いと使い方を初心者向けに解説
はじめに
C#でプログラムを書いていると、int型の値をdouble型として扱ったり、object型の変数から元の型を取り出したりする場面があります。このように、ある型の値を別の型として扱う仕組みが「キャスト」です。
C#のキャストには、変換処理を自動的に行う「暗黙的変換」と、プログラマーが変換先の型を指定する「明示的変換」があります。また、クラスやインターフェースを扱う場合には、as演算子やis演算子を利用して安全に型を確認・変換できます。
ただし、すべての型を自由にキャストできるわけではありません。誤ったキャストを行うと、InvalidCastExceptionなどの例外が発生したり、数値の一部が失われたりすることがあります。
この記事では、C#のキャストの意味と基本構文から、暗黙的変換・明示的変換・as・isの違い、よくあるエラー、安全な使い方まで、初心者向けにコード例を交えて解説します。
1. C#のキャストとは?初心者向けに意味をわかりやすく解説
1-1. キャストは「ある型を別の型として扱う」ための仕組み
C#のキャストとは、ある型の値やオブジェクトを、別の型として扱うための仕組みです。
たとえば、次のコードではint型の値をdouble型の変数に代入しています。
C#int number = 10;
double result = number;
Console.WriteLine(result); // 10
int型のnumberが、double型として扱われています。この場合は安全に変換できるため、キャストの記述は必要ありません。
一方、double型をint型に変換する場合は、小数部分が失われる可能性があります。そのため、変換先の型を明示する必要があります。
C#double price = 12.8;
int result = (int)price;
Console.WriteLine(result); // 12
(int)がキャスト演算子です。この記述によって、「priceをint型として扱う」という意図をコンパイラーに伝えています。
1-2. キャストと型変換の違い
「キャスト」と「型変換」は似た意味で使われますが、厳密には少し異なります。
型変換は、値をある型から別の型へ変換する処理全般を指します。たとえば、次の処理はすべて型変換です。
intからdoubleへの変換doubleからintへの変換stringからintへの変換派生クラスから基底クラスへの変換
object型から元の型への変換
一方、キャストは主に、キャスト演算子やas演算子を使って、値やオブジェクトを別の型として扱う操作を指します。
C#double value = 9.7;
int number = (int)value;
stringからintへの変換は、通常のキャストでは実行できません。
C#string text = "100";
// コンパイルエラー
// int number = (int)text;
この場合は、int.Parseやint.TryParseなどを使用します。
C#string text = "100";
int number = int.Parse(text);
つまり、キャストは型変換の一種ですが、すべての型変換がキャストで実現できるわけではありません。
1-3. C#でキャストが必要になる場面
C#でキャストが必要になる代表的な場面は、次のとおりです。
1つ目は、数値型を別の数値型に変換する場面です。
C#double average = 85.6;
int score = (int)average;
2つ目は、基底クラス型の変数から派生クラス固有の機能を利用する場面です。
C#Animal animal = new Dog();
Dog dog = (Dog)animal;
dog.Bark();
3つ目は、object型に格納された値を元の型へ戻す場面です。
C#object value = 123;
int number = (int)value;
4つ目は、インターフェース型の変数から、実際のクラス型を取得する場面です。
C#IRunnable runnable = new Player();
Player player = (Player)runnable;
ただし、実際のオブジェクトが変換先の型と一致しない場合、キャストは失敗します。そのため、クラスやインターフェースを扱う場合は、isによる型チェックやパターンマッチングを利用するのが安全です。
1-4. キャストを理解するために必要な「型」の基本
C#では、変数や値に型があります。型は、その値がどのようなデータであり、どのような操作を行えるかを決めます。
代表的な値型には、次のものがあります。
C#int age = 20;
double height = 170.5;
bool isActive = true;
char grade = 'A';
代表的な参照型には、次のものがあります。
C#string name = "田中";
object value = new object();
Person person = new Person();
C#の型は、大きく「値型」と「参照型」に分けられます。
値型の変数には、基本的に値そのものが格納されます。int、double、bool、構造体などが該当します。
参照型の変数には、オブジェクトを参照するための情報が格納されます。クラス、配列、デリゲート、stringなどが該当します。
キャストの可否や動作は、値型か参照型か、型同士に継承関係があるかなどによって異なります。
2. C#のキャストの基本構文
2-1. キャスト演算子「(型名)値」の書き方
C#で明示的なキャストを行う基本構文は、次のとおりです。
C#変換先の型 変数名 = (変換先の型)変換する値;
たとえば、double型をint型にキャストする場合は、次のように記述します。
C#double value = 10.9;
int number = (int)value;
キャストする式が複雑な場合は、括弧で囲むと意図がわかりやすくなります。
C#double price = 100.5;
double tax = 10.2;
int total = (int)(price + tax);
Console.WriteLine(total); // 110
キャスト演算子は、変換対象の値の直前に記述します。
2-2. int・doubleなど数値型のキャスト例
数値型同士では、多くの場合、明示的なキャストが利用できます。
C#double value = 123.9;
int number = (int)value;
Console.WriteLine(number); // 123
doubleからintへキャストすると、小数部分は四捨五入されず、0に近い方向へ切り捨てられます。
C#double positive = 3.9;
double negative = -3.9;
Console.WriteLine((int)positive); // 3
Console.WriteLine((int)negative); // -3
intからbyteのように、より小さな範囲の型へ変換することもできます。
C#int number = 100;
byte value = (byte)number;
Console.WriteLine(value); // 100
ただし、変換先の範囲を超える値には注意が必要です。
C#int number = 300;
byte value = (byte)number;
Console.WriteLine(value); // uncheckedコンテキストでは44
byteが扱える範囲は0~255です。通常の未チェックコンテキストで範囲外の整数をキャストすると、上位ビットが失われ、予想外の値になることがあります。
2-3. object型から元の型に戻すキャスト例
C#では、値型をobject型へ代入できます。この処理を「ボックス化」と呼びます。
C#int number = 100;
object value = number;
object型に格納した値を元の値型へ戻す処理は「ボックス化解除」と呼ばれ、明示的なキャストが必要です。
C#object value = 100;
int number = (int)value;
Console.WriteLine(number); // 100
ボックス化解除では、実際に格納されている値型と、キャスト先の型が一致している必要があります。
C#object value = 100;
// InvalidCastException
// long number = (long)value;
valueの中身はint型なので、直接long型へボックス化解除することはできません。いったんint型として取り出してから変換します。
C#object value = 100;
long number = (long)(int)value;
Console.WriteLine(number); // 100
または、用途に応じてConvert.ToInt64を利用できます。
C#object value = 100;
long number = Convert.ToInt64(value);
2-4. キャストできる型とできない型の違い
C#でキャストできる主な組み合わせには、次のものがあります。
数値型同士
継承関係にあるクラス同士
クラスと、そのクラスが実装するインターフェース
object型と、実際に格納されている型変換演算子が定義されているユーザー定義型同士
たとえば、継承関係がある型はキャストできます。
C#class Animal
{
}
class Dog : Animal
{
}
Animal animal = new Dog();
Dog dog = (Dog)animal;
一方、継承関係も変換規則もない型同士はキャストできません。
C#class Dog
{
}
class Car
{
}
Dog dog = new Dog();
// コンパイルエラー
// Car car = (Car)dog;
また、stringとintのように値の表現方法が異なる型は、通常のキャストでは変換できません。
C#string text = "123";
// コンパイルエラー
// int number = (int)text;
この場合は、ParseやTryParseを使って文字列を解析します。
3. 暗黙的変換とは?キャストを書かなくても変換できるケース
3-1. 暗黙的変換の意味
暗黙的変換とは、キャスト演算子を記述しなくても、コンパイラーが自動的に行う型変換です。
C#int number = 10;
long result = number;
このコードでは、int型からlong型へ自動的に変換されています。
暗黙的変換は、基本的に値が失われる危険性が低く、安全に変換できる場合に許可されます。
3-2. intからlong、floatからdoubleなど安全な変換
int型の値は、通常、long型の範囲内に収まります。そのため、次の変換ではキャストが必要ありません。
C#int number = 1_000;
long result = number;
float型からdouble型への変換も、暗黙的に実行できます。
C#float value = 12.5f;
double result = value;
代表的な暗黙的数値変換には、次のようなものがあります。
byteからshort、int、long、float、double、decimalshortからint、long、float、double、decimalintからlong、float、double、decimallongからfloat、double、decimalfloatからdouble
ただし、「暗黙的に変換できること」と「すべての値を完全に同じ精度で表現できること」は必ずしも同じではありません。
たとえば、非常に大きなintをfloatへ変換すると、浮動小数点数の精度の都合で下位の桁が正確に保持されない場合があります。
C#int number = 16_777_217;
float result = number;
Console.WriteLine(result); // 表現精度の影響を受ける可能性がある
3-3. 派生クラスから基底クラスへのアップキャスト
派生クラス型から基底クラス型への変換を「アップキャスト」と呼びます。
C#class Animal
{
public void Eat()
{
Console.WriteLine("食べます");
}
}
class Dog : Animal
{
public void Bark()
{
Console.WriteLine("吠えます");
}
}
DogはAnimalを継承しているため、Dog型のオブジェクトはAnimal型として扱えます。
C#Dog dog = new Dog();
Animal animal = dog;
アップキャストは安全なので、明示的なキャストは必要ありません。
ただし、変数の型がAnimalになると、Animal型に定義されているメンバーだけを直接利用できます。
C#animal.Eat();
// コンパイルエラー
// animal.Bark();
実際のオブジェクトはDogのままですが、コンパイラーは変数の宣言型で利用できるメンバーを判断します。
3-4. 暗黙的変換が許可される理由
暗黙的変換が許可されるのは、変換が基本的に安全であり、プログラマーが明示しなくても意図を判断しやすいからです。
たとえば、intからlongへの変換では、intが表現できる整数範囲をlongがすべて含んでいます。
また、派生クラスのオブジェクトは、必ず基底クラスとしての性質を持っています。
C#Dog dog = new Dog();
Animal animal = dog;
すべてのDogはAnimalですが、すべてのAnimalがDogとは限りません。そのため、逆方向の変換には明示的な確認やキャストが必要です。
3-5. 暗黙的変換のコード例
数値型の暗黙的変換は次のように記述できます。
C#int intValue = 500;
long longValue = intValue;
double doubleValue = longValue;
Console.WriteLine(longValue); // 500
Console.WriteLine(doubleValue); // 500
クラスのアップキャストは次のように記述します。
C#class Animal
{
public virtual void Speak()
{
Console.WriteLine("動物の鳴き声");
}
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワン");
}
}
Dog dog = new Dog();
Animal animal = dog;
animal.Speak(); // ワン
変数の型はAnimalですが、実行時には実際のオブジェクトであるDogのオーバーライドされたメソッドが呼び出されます。
4. 明示的変換とは?キャストが必要になるケース
4-1. 明示的変換の意味
明示的変換とは、プログラマーが変換先の型を指定して行う型変換です。
C#double value = 10.5;
int number = (int)value;
変換によって情報が失われる可能性がある場合や、実行時に失敗する可能性がある場合は、明示的なキャストが必要になります。
明示的な記述を要求することで、コンパイラーはプログラマーが危険性を理解したうえで変換していると判断します。
4-2. doubleからintなど情報が失われる可能性がある変換
double型は小数を扱えますが、int型は整数しか扱えません。そのため、doubleからintへの変換では小数部分が失われます。
C#double value = 98.7;
int number = (int)value;
Console.WriteLine(number); // 98
四捨五入したい場合は、単純なキャストではなく、Math.Roundなどを使用します。
C#double value = 98.7;
int number = (int)Math.Round(value);
Console.WriteLine(number); // 99
切り上げや切り捨てを明確に指定したい場合は、次のメソッドを利用できます。
C#double value = 98.2;
Console.WriteLine(Math.Ceiling(value)); // 99
Console.WriteLine(Math.Floor(value)); // 98
Console.WriteLine(Math.Truncate(value)); // 98
4-3. 基底クラスから派生クラスへのダウンキャスト
基底クラス型から派生クラス型への変換を「ダウンキャスト」と呼びます。
C#Animal animal = new Dog();
Dog dog = (Dog)animal;
変数animalの宣言型はAnimalですが、実際に参照しているオブジェクトはDogなので、キャストは成功します。
しかし、実際のオブジェクトがDogでない場合は失敗します。
C#Animal animal = new Animal();
// 実行時にInvalidCastException
Dog dog = (Dog)animal;
コンパイラーは、animalが実行時にどの派生クラスのオブジェクトを参照するかを常に判断できません。そのため、ダウンキャストの成否は実行時に確認されます。
4-4. 明示的キャストで発生しやすい例外
参照型の明示的キャストに失敗すると、主にInvalidCastExceptionが発生します。
C#object value = "C#";
// InvalidCastException
int number = (int)value;
valueの実際の型はstringですが、int型へキャストしようとしているためです。
一方、nullを参照型へキャストすること自体は可能です。
C#object? value = null;
string? text = (string?)value;
Console.WriteLine(text is null); // True
ただし、nullを非Nullable値型へキャストすると例外が発生します。
C#object? value = null;
// NullReferenceException
// int number = (int)value;
数値の明示的変換では、checkedコンテキストで値が変換先の範囲を超えると、OverflowExceptionが発生します。
C#int number = 300;
checked
{
byte value = (byte)number; // OverflowException
}
4-5. 明示的変換のコード例
数値型の明示的変換は次のように行います。
C#long longValue = 1_000;
int intValue = (int)longValue;
double doubleValue = 12.9;
int truncatedValue = (int)doubleValue;
Console.WriteLine(intValue); // 1000
Console.WriteLine(truncatedValue); // 12
クラスのダウンキャストは次のように記述できます。
C#class Animal
{
}
class Dog : Animal
{
public void Bark()
{
Console.WriteLine("ワン");
}
}
Animal animal = new Dog();
Dog dog = (Dog)animal;
dog.Bark();
ダウンキャストが必ず成功する保証がない場合は、通常のキャストよりもisパターンを利用したほうが安全です。
5. as演算子によるキャストの使い方
5-1. as演算子とは
as演算子は、オブジェクトを指定した型へ変換するときに使用する演算子です。
通常のキャストは変換に失敗するとInvalidCastExceptionを発生させますが、as演算子は失敗した場合にnullを返します。
C#object value = "C#";
string? text = value as string;
Console.WriteLine(text); // C#
変換できない場合は次のようになります。
C#object value = 100;
string? text = value as string;
Console.WriteLine(text is null); // True
例外ではなくnullで失敗を判定できる点が、通常のキャストとの大きな違いです。
5-2. as演算子の基本構文
as演算子の基本構文は次のとおりです。
C#変換先の型? 変数名 = 値 as 変換先の型;
クラスのキャストでは、次のように記述します。
C#Animal animal = new Dog();
Dog? dog = animal as Dog;
キャストが成功すればdogにDogオブジェクトへの参照が入り、失敗すればnullが入ります。
C#if (dog is not null)
{
dog.Bark();
}
Nullable参照型を有効にしているプロジェクトでは、キャストが失敗してnullになる可能性を示すため、変数をDog?のように宣言するのが適切です。
5-3. キャストに失敗するとnullになる仕組み
as演算子は、実行時にオブジェクトが変換先の型と互換性を持つか確認します。
互換性がある場合は、変換後の参照を返します。
C#object value = "hello";
string? text = value as string;
Console.WriteLine(text); // hello
互換性がない場合は、例外を投げずにnullを返します。
C#object value = 123;
string? text = value as string;
if (text is null)
{
Console.WriteLine("string型へ変換できませんでした");
}
そのため、asを使った後は必ずnullの可能性を考慮する必要があります。
5-4. as演算子が使える型と使えない型
as演算子は、主に次の型に使用できます。
クラスなどの参照型
インターフェース型
配列型
Nullable値型
適切な制約を持つ型パラメーター
通常の非Nullable値型には使用できません。
C#object value = 100;
// コンパイルエラー
// int number = value as int;
Nullable値型であれば使用できます。
C#object value = 100;
int? number = value as int?;
Console.WriteLine(number); // 100
ただし、数値型同士を変換する目的でasを使用することはできません。
C#object value = 100;
// intとして格納された値をlong?へ直接変換することはできない
long? number = value as long?;
Console.WriteLine(number is null); // True
as演算子はユーザー定義の変換演算子を実行しない点にも注意が必要です。
5-5. as演算子を使うべき場面
as演算子は、キャストできないことが通常の処理として想定され、その場合にnullとして扱いたい場面に向いています。
C#object value = GetValue();
Customer? customer = value as Customer;
if (customer is null)
{
Console.WriteLine("顧客データではありません");
return;
}
Console.WriteLine(customer.Name);
ただし、キャスト後すぐに型を確認して処理するだけであれば、現在のC#ではisパターンマッチングのほうが簡潔です。
C#if (value is Customer customer)
{
Console.WriteLine(customer.Name);
}
asは、変換結果を後続の複数箇所で利用したい場合や、nullを変換失敗の結果として保持したい場合に適しています。
6. is演算子による型チェックの使い方
6-1. is演算子とは
is演算子は、値やオブジェクトが指定した型と互換性を持つか確認するための演算子です。
C#object value = "C#";
bool result = value is string;
Console.WriteLine(result); // True
指定した型として扱える場合はtrue、扱えない場合はfalseを返します。
C#object value = 123;
Console.WriteLine(value is int); // True
Console.WriteLine(value is string); // False
nullに対する型チェックはfalseになります。
C#object? value = null;
Console.WriteLine(value is string); // False
6-2. キャスト前に型を確認する方法
従来は、isで型を確認した後、通常のキャストを行う書き方が使われていました。
C#object value = "hello";
if (value is string)
{
string text = (string)value;
Console.WriteLine(text.Length);
}
この書き方でも安全ですが、型チェックとキャストを別々に記述する必要があります。
現在のC#では、パターンマッチングを使用して、型チェックと変数への代入を同時に行う方法が一般的です。
C#if (value is string text)
{
Console.WriteLine(text.Length);
}
この場合、条件が成立したブロック内では、textをstring型として安全に利用できます。
6-3. is演算子とパターンマッチングの基本
is演算子の型パターンを使うと、型の確認と変数の宣言を同時に実行できます。
C#object value = 100;
if (value is int number)
{
Console.WriteLine(number * 2); // 200
}
条件を追加することもできます。
C#object value = 120;
if (value is int number && number >= 100)
{
Console.WriteLine("100以上の整数です");
}
プロパティパターンを使えば、型とプロパティの条件をまとめて確認できます。
C#class User
{
public string Name { get; set; } = "";
public int Age { get; set; }
}
object value = new User
{
Name = "佐藤",
Age = 20
};
if (value is User { Age: >= 18 } user)
{
Console.WriteLine($"{user.Name}は成人です");
}
複数の型によって処理を分岐する場合は、switch式やswitch文のパターンマッチングも便利です。
C#static string Describe(object value)
{
return value switch
{
int number => $"整数: {number}",
string text => $"文字列: {text}",
null => "null",
_ => "その他の型"
};
}
6-4. isを使って安全にキャストするコード例
次の例では、Animal型の変数が実際にDogオブジェクトを参照している場合だけ、Dog固有のメソッドを実行します。
C#class Animal
{
}
class Dog : Animal
{
public void Bark()
{
Console.WriteLine("ワン");
}
}
Animal animal = new Dog();
if (animal is Dog dog)
{
dog.Bark();
}
キャストできない場合は条件がfalseになるため、InvalidCastExceptionは発生しません。
否定パターンを使って、対象外の型を先に除外することもできます。
C#if (animal is not Dog dog)
{
Console.WriteLine("Dog型ではありません");
return;
}
dog.Bark();
このような早期リターンを利用すると、正常な処理のネストを浅くできます。
6-5. is演算子を使うべき場面
is演算子は、次のような場面に向いています。
オブジェクトの実際の型によって処理を分けたい
キャストが成功した場合だけ処理を実行したい
型チェックと変数への代入を同時に行いたい
複数の型や条件をパターンマッチングで判定したい
キャスト失敗による例外を避けたい
特に、キャスト後の値をifブロック内で使用する場合は、isパターンマッチングが読みやすく安全です。
C#if (value is Customer customer)
{
SendEmail(customer);
}
7. 「(型)」「as」「is」の違いと使い分け
7-1. 通常のキャスト「(型)」の特徴
通常のキャストは、次のように記述します。
C#Dog dog = (Dog)animal;
主な特徴は次のとおりです。
変換結果を指定した型として取得できる
数値型の明示的変換に使用できる
参照型のキャストに使用できる
キャストできない参照型の場合は
InvalidCastExceptionが発生するキャストできることが保証されている場面に向いている
失敗がプログラム上の異常を意味する場合は、通常のキャストを使うことで問題をすぐに検出できます。
7-2. as演算子の特徴
as演算子は次のように記述します。
C#Dog? dog = animal as Dog;
主な特徴は次のとおりです。
キャストに成功すると、変換後の参照を返す
キャストに失敗すると
nullを返す主に参照型やNullable値型に使用する
数値型同士の通常の変換には使用できない
使用後に
nullチェックが必要になるユーザー定義変換は実行しない
キャスト失敗を例外ではなくnullで扱いたい場合に適しています。
7-3. is演算子の特徴
is演算子は、対象が指定した型として扱えるかを確認します。
C#bool isDog = animal is Dog;
パターンマッチングを使えば、型チェックと変数への代入を同時に行えます。
C#if (animal is Dog dog)
{
dog.Bark();
}
主な特徴は次のとおりです。
型の互換性を
trueまたはfalseで判定するパターンマッチングで変換後の変数を取得できる
型に応じた条件分岐に向いている
失敗しても例外が発生しない
nullは通常の型パターンに一致しない
7-4. キャスト失敗時の挙動の違い
通常のキャスト、as、isでは、キャストできない場合の動作が異なります。
C#object value = "C#";
通常のキャストでは、失敗すると例外が発生します。
C#// InvalidCastException
int number = (int)value;
as演算子では、失敗するとnullになります。
C#int? number = value as int?;
Console.WriteLine(number is null); // True
is演算子では、型が一致しない場合にfalseになります。
C#if (value is int number)
{
Console.WriteLine(number);
}
else
{
Console.WriteLine("int型ではありません");
}
7-5. 初心者向けの使い分け早見表
| 方法 | 主な用途 | 失敗時の動作 | 数値変換 | null確認 |
|---|---|---|---|---|
(型)値 | 確実なキャスト、数値変換 | 主に例外または値の変化 | 可能 | 状況による |
値 as 型 | 失敗をnullで受け取りたい | nullを返す | 通常は不可 | 必要 |
値 is 型 変数 | 型を確認して処理を分岐 | falseになる | 型判定は可能 | パターン内では不要 |
数値型を変換する場合は、基本的に通常のキャストを使用します。
C#int number = (int)12.5;
参照型を確認し、成功したときだけ処理したい場合は、isパターンマッチングが便利です。
C#if (value is Customer customer)
{
Console.WriteLine(customer.Name);
}
キャスト結果を変数に保存し、失敗時にnullとして後続処理へ渡したい場合はasが選択肢になります。
C#Customer? customer = value as Customer;
7-6. 実務でおすすめの使い分け
実務では、キャストが成功することが設計上保証されている場合に通常のキャストを使用します。
C#object value = 100;
int number = (int)value;
成功するか不明で、成功時だけ処理したい場合は、isパターンマッチングが適しています。
C#if (value is int number)
{
Console.WriteLine(number);
}
失敗した結果をnullとして保持する必要がある場合は、asを使用します。
C#Customer? customer = value as Customer;
ただし、asを使ってすぐにnullチェックするだけなら、次のコードよりも、
C#Customer? customer = value as Customer;
if (customer is not null)
{
Console.WriteLine(customer.Name);
}
isパターンを使ったほうが簡潔です。
C#if (value is Customer customer)
{
Console.WriteLine(customer.Name);
}
8. C#のキャストでよくあるエラーと対処法
8-1. InvalidCastExceptionが発生する原因
InvalidCastExceptionは、実際のオブジェクトを互換性のない型へキャストした場合に発生します。
C#object value = "123";
// InvalidCastException
int number = (int)value;
文字列の内容が数字であっても、実際の型はstringです。通常のキャストではintに変換できません。
C#int number = int.Parse((string)value);
また、基底クラスから誤った派生クラスへキャストした場合にも発生します。
C#class Animal
{
}
class Dog : Animal
{
}
class Cat : Animal
{
}
Animal animal = new Cat();
// InvalidCastException
Dog dog = (Dog)animal;
対策として、isパターンマッチングを使用します。
C#if (animal is Dog dog)
{
Console.WriteLine("Dog型です");
}
else
{
Console.WriteLine("Dog型ではありません");
}
8-2. NullReferenceExceptionにつながるasの注意点
as演算子は、変換に失敗するとnullを返します。その結果を確認せずに使用すると、NullReferenceExceptionにつながります。
C#object value = 100;
string? text = value as string;
// textはnullなので危険
// Console.WriteLine(text.Length);
nullチェックを行ってから使用します。
C#if (text is not null)
{
Console.WriteLine(text.Length);
}
または、null条件演算子を利用します。
C#Console.WriteLine(text?.Length);
必須の値であれば、nullの場合に明示的な例外を発生させる方法もあります。
C#string text = value as string
?? throw new InvalidOperationException("string型の値が必要です");
ただし、キャスト後すぐに処理する場合は、isパターンのほうが安全で簡潔です。
8-3. 数値変換で値が丸められる・切り捨てられるケース
doubleやdecimalから整数型へキャストすると、小数部分は0に近い方向へ切り捨てられます。
C#double value = 9.9;
int number = (int)value;
Console.WriteLine(number); // 9
負の値も0に近い方向へ切り捨てられます。
C#double value = -9.9;
int number = (int)value;
Console.WriteLine(number); // -9
四捨五入が必要な場合は、Math.Roundを使います。
C#double value = 9.9;
int number = (int)Math.Round(value);
Console.WriteLine(number); // 10
Convert.ToInt32(double)も利用できますが、単純なキャストとは丸め方が異なります。
C#double value = 9.9;
Console.WriteLine((int)value); // 9
Console.WriteLine(Convert.ToInt32(value)); // 10
Convert.ToInt32は小数値を最も近い整数へ丸めます。ちょうど中間の値では、既定で偶数側へ丸める動作があるため、必要な丸め規則を確認して使用しましょう。
8-4. オーバーフローが起きるケース
大きな数値を小さな範囲の型へキャストすると、オーバーフローが発生する可能性があります。
C#int number = 300;
byte value = (byte)number;
Console.WriteLine(value); // uncheckedでは44
通常の未チェックコンテキストでは、整数変換時に範囲外の部分が失われ、別の値になることがあります。
オーバーフローを例外として検出したい場合は、checkedを使います。
C#int number = 300;
try
{
byte value = checked((byte)number);
}
catch (OverflowException)
{
Console.WriteLine("byte型の範囲を超えています");
}
明示的に未チェックで変換したい場合は、uncheckedを使用できます。
C#int number = 300;
byte value = unchecked((byte)number);
業務データや金額など、値の正確性が重要な場面では、範囲確認やcheckedの利用を検討しましょう。
8-5. エラーを防ぐための安全な書き方
参照型のキャストでは、isパターンマッチングを利用すると安全です。
C#if (value is Customer customer)
{
Console.WriteLine(customer.Name);
}
数値型では、変換前に範囲を確認できます。
C#int number = 300;
if (number >= byte.MinValue && number <= byte.MaxValue)
{
byte value = (byte)number;
Console.WriteLine(value);
}
else
{
Console.WriteLine("byte型の範囲外です");
}
文字列を数値に変換するときは、TryParseを利用します。
C#string input = "123";
if (int.TryParse(input, out int number))
{
Console.WriteLine(number);
}
else
{
Console.WriteLine("整数へ変換できません");
}
例外を避けることだけを目的にするのではなく、「変換失敗が通常の結果なのか、プログラム上の異常なのか」を考えて方法を選ぶことが大切です。
9. キャストとConvert・Parse・TryParseの違い
9-1. キャストでは文字列を数値に変換できない
文字列"100"と整数100は、見た目が似ていても異なる型のデータです。
C#string text = "100";
次のようなキャストはできません。
C#// コンパイルエラー
// int number = (int)text;
文字列を数値に変換するには、文字列の内容を解析する必要があります。そのため、Convert、Parse、TryParseなどを使用します。
C#int number = int.Parse(text);
9-2. Convert.ToInt32の使い方
Convert.ToInt32は、さまざまな型の値をint型へ変換するためのメソッドです。
C#string text = "123";
int number = Convert.ToInt32(text);
Console.WriteLine(number); // 123
double型にも使用できます。
C#double value = 12.7;
int number = Convert.ToInt32(value);
Console.WriteLine(number); // 13
nullを含む文字列を渡した場合は0になります。
C#string? text = null;
int number = Convert.ToInt32(text);
Console.WriteLine(number); // 0
ただし、数値として解釈できない文字列ではFormatException、変換先の範囲を超える値ではOverflowExceptionが発生します。
C#// FormatException
// int number = Convert.ToInt32("abc");
9-3. int.Parseの使い方
int.Parseは、数値形式の文字列をint型へ変換します。
C#string text = "456";
int number = int.Parse(text);
Console.WriteLine(number); // 456
変換対象が不正な形式の場合はFormatExceptionが発生します。
C#// FormatException
// int number = int.Parse("12.5");
nullを渡した場合はArgumentNullExceptionが発生します。
C#string? text = null;
// ArgumentNullException
// int number = int.Parse(text);
変換に失敗する可能性がなく、不正な入力を例外として扱いたい場合に利用できます。
9-4. int.TryParseの使い方
int.TryParseは、文字列からint型への変換を試み、成功したかどうかをbool値で返します。
C#string text = "789";
bool success = int.TryParse(text, out int number);
Console.WriteLine(success); // True
Console.WriteLine(number); // 789
変換に失敗しても、通常は例外を発生させません。
C#string text = "abc";
if (int.TryParse(text, out int number))
{
Console.WriteLine(number);
}
else
{
Console.WriteLine("整数を入力してください");
}
失敗した場合、out変数には既定値の0が入ります。ただし、成功・失敗は戻り値で判断するべきです。
ユーザー入力、CSV、外部APIなど、形式が保証されていない文字列を扱う場合は、TryParseが適しています。
9-5. キャスト・Convert・Parse・TryParseの使い分け
キャストは、数値型同士や継承関係にある型など、C#で変換規則が定義されている型に使用します。
C#double value = 10.8;
int number = (int)value;
Convertは、さまざまな基本型を別の基本型へ変換したい場合に利用できます。
C#string text = "100";
int number = Convert.ToInt32(text);
Parseは、正しい形式であることが前提の文字列を特定の型へ解析するときに使用します。
C#int number = int.Parse("100");
TryParseは、変換に失敗する可能性があり、例外を発生させずに結果を確認したい場合に使用します。
C#if (int.TryParse(input, out int number))
{
Console.WriteLine(number);
}
実務では、外部から受け取った文字列に対しては、基本的にTryParseを優先すると安全です。
10. クラス・継承・インターフェースでのキャスト
10-1. アップキャストとは
アップキャストとは、派生クラス型を基底クラス型として扱うことです。
C#class Animal
{
public virtual void Speak()
{
Console.WriteLine("鳴きます");
}
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワン");
}
public void Fetch()
{
Console.WriteLine("ボールを取ります");
}
}
DogはAnimalを継承しているため、暗黙的に変換できます。
C#Dog dog = new Dog();
Animal animal = dog;
アップキャスト後は、変数の宣言型であるAnimalのメンバーを利用できます。
C#animal.Speak(); // ワン
FetchはDog固有のメソッドなので、Animal型の変数から直接呼び出すことはできません。
10-2. ダウンキャストとは
ダウンキャストとは、基底クラス型の変数を派生クラス型として扱うことです。
C#Animal animal = new Dog();
Dog dog = (Dog)animal;
dog.Fetch();
実際のオブジェクトが変換先の型でなければ、InvalidCastExceptionが発生します。
C#Animal animal = new Animal();
// InvalidCastException
// Dog dog = (Dog)animal;
安全にダウンキャストするには、isパターンマッチングを使用します。
C#if (animal is Dog dog)
{
dog.Fetch();
}
ダウンキャストが頻繁に必要になる設計では、基底クラスやインターフェースに必要な振る舞いを定義できないか検討することも重要です。
10-3. インターフェース型へのキャスト
クラスがインターフェースを実装している場合、そのクラスのオブジェクトはインターフェース型として扱えます。
C#interface IRunnable
{
void Run();
}
class Player : IRunnable
{
public void Run()
{
Console.WriteLine("プレイヤーが走ります");
}
public void Jump()
{
Console.WriteLine("ジャンプします");
}
}
クラス型からインターフェース型への変換は暗黙的に行えます。
C#Player player = new Player();
IRunnable runnable = player;
runnable.Run();
インターフェース型から実装クラス型へ戻す場合は、キャストが必要です。
C#Player restoredPlayer = (Player)runnable;
restoredPlayer.Jump();
実際のオブジェクトがPlayerである保証がない場合は、isを使います。
C#if (runnable is Player actualPlayer)
{
actualPlayer.Jump();
}
10-4. object型を扱うときのキャスト
objectは、C#のすべての型の基底となる型です。そのため、多くの値をobject型の変数に代入できます。
C#object text = "C#";
object number = 100;
object user = new User();
object型から具体的な型として利用するには、キャストや型チェックが必要です。
C#object value = "C#";
string text = (string)value;
Console.WriteLine(text.Length);
実際の型が不明な場合は、isパターンを利用します。
C#object value = GetValue();
switch (value)
{
case string text:
Console.WriteLine($"文字列: {text}");
break;
case int number:
Console.WriteLine($"整数: {number}");
break;
case null:
Console.WriteLine("nullです");
break;
default:
Console.WriteLine($"その他: {value.GetType().Name}");
break;
}
objectを多用すると、コンパイル時の型チェックを活かしにくくなります。扱う型が決まっている場合は、ジェネリック型や共通インターフェースの利用を検討しましょう。
10-5. 継承関係がない型へのキャストができない理由
継承関係や実装関係がないクラス同士には、型としての互換性がありません。
C#class Dog
{
}
class Car
{
}
Dog dog = new Dog();
// コンパイルエラー
// Car car = (Car)dog;
Dogオブジェクトには、Carとして必要な構造や振る舞いが保証されていないためです。
見た目のプロパティが似ていても、別の型として定義されていれば直接キャストできません。
C#class UserEntity
{
public string Name { get; set; } = "";
}
class UserDto
{
public string Name { get; set; } = "";
}
この場合は、必要な値を明示的にコピーします。
C#UserEntity entity = new UserEntity
{
Name = "山田"
};
UserDto dto = new UserDto
{
Name = entity.Name
};
型同士の変換を共通化したい場合は、変換用メソッド、コンストラクター、マッピング処理、ユーザー定義変換演算子などを利用できます。
11. C#のキャストを安全に使うためのベストプラクティス
11-1. 不要なキャストは避ける
キャストを多用すると、コードが複雑になり、実行時エラーの原因が増えます。暗黙的に変換できる場合は、不要なキャストを書かないようにしましょう。
C#int number = 100;
// 不要
long value1 = (long)number;
// こちらで十分
long value2 = number;
参照型のダウンキャストが繰り返し必要になる場合は、設計を見直す余地があります。
たとえば、派生クラスごとの処理を外部から型判定して実行するのではなく、基底クラスの仮想メソッドとして定義できる場合があります。
C#abstract class Animal
{
public abstract void Speak();
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワン");
}
}
class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("ニャー");
}
}
呼び出し側は具体的な型を確認する必要がありません。
C#Animal animal = new Dog();
animal.Speak();
11-2. 失敗する可能性がある場合はisやasを使う
キャストが成功するかわからない場合は、通常のキャストを無条件に実行しないようにします。
キャスト後すぐに処理する場合は、isパターンが適しています。
C#if (value is Customer customer)
{
Console.WriteLine(customer.Name);
}
変換結果をnullとして保持したい場合は、asを利用できます。
C#Customer? customer = value as Customer;
if (customer is null)
{
Console.WriteLine("Customer型ではありません");
return;
}
ただし、失敗が本来起きてはいけない不具合を意味する場合は、例外が発生する通常のキャストを選ぶことにも意味があります。失敗を何でもnullとして隠さないことが重要です。
11-3. 数値変換では値の範囲に注意する
数値型を小さな型へ変換するときは、変換先の範囲を確認しましょう。
C#long value = 3_000_000_000;
if (value >= int.MinValue && value <= int.MaxValue)
{
int number = (int)value;
Console.WriteLine(number);
}
else
{
Console.WriteLine("int型の範囲外です");
}
確実にオーバーフローを検出したい場合は、checkedを使います。
C#try
{
int number = checked((int)value);
}
catch (OverflowException)
{
Console.WriteLine("数値が大きすぎます");
}
また、浮動小数点型から整数型へ変換すると小数部分が失われるため、切り捨て・切り上げ・四捨五入のどれが必要か明確にしましょう。
11-4. 文字列変換ではTryParseを優先する
ユーザー入力や外部データの文字列は、必ずしも正しい形式とは限りません。
C#string input = Console.ReadLine() ?? "";
このような値にint.Parseを使用すると、不正な入力によって例外が発生します。
C#// 入力が数値でなければ例外
// int age = int.Parse(input);
TryParseを利用すれば、安全に入力を検証できます。
C#if (int.TryParse(input, out int age))
{
Console.WriteLine($"年齢は{age}歳です");
}
else
{
Console.WriteLine("年齢を整数で入力してください");
}
日付や小数にも、それぞれTryParseが用意されています。
C#if (DateTime.TryParse("2026/06/20", out DateTime date))
{
Console.WriteLine(date);
}
if (decimal.TryParse("1234.56", out decimal price))
{
Console.WriteLine(price);
}
11-5. 読みやすいコードにするための考え方
キャストを記述するときは、変換の意図が読み手に伝わるようにすることが大切です。
たとえば、次のコードは短いものの、キャストの理由がわかりにくい場合があります。
C#int result = (int)value;
変数名や処理を工夫すると、意図が明確になります。
C#int truncatedPrice = (int)price;
四捨五入が目的であれば、その処理をコードに明示します。
C#int roundedPrice = (int)Math.Round(price);
参照型のキャストでは、パターンマッチングを利用すると、型チェックと処理の関係がわかりやすくなります。
C#if (message is TextMessage textMessage)
{
DisplayText(textMessage.Text);
}
キャストを安全に使うためには、単に例外を避けるだけでなく、次の点を意識することが重要です。
なぜ別の型へ変換する必要があるのか
変換によって情報が失われないか
キャストが失敗する可能性はあるか
失敗は通常の結果か、プログラム上の異常か
キャストせずに解決できる設計はないか
まとめ
C#のキャストは、ある型の値やオブジェクトを別の型として扱うための仕組みです。
安全な変換では、キャストを記述しない暗黙的変換が利用できます。
C#int number = 100;
long value = number;
情報が失われる可能性がある数値変換や、基底クラスから派生クラスへのダウンキャストでは、明示的なキャストが必要です。
C#double value = 10.9;
int number = (int)value;
通常のキャストは、変換に失敗するとInvalidCastExceptionが発生する可能性があります。失敗する可能性がある参照型のキャストでは、asやisを利用できます。
C#Customer? customer = value as Customer;
C#if (value is Customer customer)
{
Console.WriteLine(customer.Name);
}
現在のC#では、型を確認してすぐに処理する場合、isパターンマッチングが安全で読みやすい方法です。
また、文字列から数値への変換は通常のキャストでは行えません。外部入力を変換するときは、TryParseを優先しましょう。
C#if (int.TryParse(input, out int number))
{
Console.WriteLine(number);
}
C#のキャストを使うときは、型同士の関係、変換失敗時の動作、数値の範囲、情報が失われる可能性を確認することが重要です。不要なキャストを避け、目的に応じて通常のキャスト、as、is、Convert、Parse、TryParseを使い分けることで、安全で読みやすいコードを作成できます。

