C#のデータ型一覧|基本型・参照型・変換方法を初心者向けに解説
はじめに
C#でプログラムを書くとき、変数や値には必ず「データ型」があります。整数を扱うint、文字列を扱うstring、真偽を表すboolなどが代表例です。
データ型を正しく理解すると、次のようなメリットがあります。
数値や文字列を適切に扱える
意図しない計算結果やエラーを防げる
メモリを効率的に利用できる
読みやすく保守しやすいコードを書ける
型変換やnullに関する問題へ対応できる
本記事では、C#のデータ型を値型と参照型に分けて整理し、基本型の一覧、変数の宣言方法、型変換、nullable型まで初心者向けに解説します。
1. C#のデータ型とは
1-1. データ型の基本的な役割
データ型とは、変数に保存するデータの種類や扱い方を決める仕組みです。
たとえば、年齢は整数、商品名は文字列、ログイン状態は真偽値として表現できます。
int age = 20;string productName = "ノートパソコン";bool isLoggedIn = true;この例では、それぞれ次のデータ型を使用しています。
int:整数string:文字列bool:trueまたはfalse
データ型によって、保存できる値の範囲や利用できる演算が異なります。
int price = 1000;int quantity = 3;int total = price * quantity;int型は数値を扱うため、加算や乗算が可能です。一方、文字列では+演算子が文字列の連結として働きます。
string firstName = "Taro";string lastName = "Yamada";string fullName = firstName + " " + lastName;1-2. C#でデータ型を理解する重要性
C#は、変数や式の型を厳密に確認する「静的型付け言語」です。多くの型エラーは、プログラムを実行する前のコンパイル時に検出されます。
たとえば、次のコードはコンパイルできません。
int age = "20";int型の変数にstring型の値を直接代入しているためです。文字列を整数に変換するには、int.Parseやint.TryParseなどを使います。
int age = int.Parse("20");型を理解していれば、データに適した処理方法を選びやすくなります。また、コンパイラによる型チェックを活用できるため、実行時の不具合も減らせます。
1-3. 初心者が混乱しやすい「型」と「データ」の関係
型は「データを入れる箱の種類」、データは「箱の中身」と考えると理解しやすくなります。
int score = 80;このコードでは、次のような関係になっています。
int:箱の種類score:箱の名前80:箱に入っているデータ
ただし、型によって入れられるデータは決まっています。
int score = 80; // 正しいstring name = "Yamada"; // 正しいbool isPassed = true; // 正しい次のように、型と値の種類が一致しない代入はできません。
int score = "80"; // エラーbool isPassed = 1; // エラーC#では、整数の1を自動的にtrueとして扱うこともありません。異なる種類のデータを扱う場合は、必要に応じて明示的な型変換を行います。
2. C#のデータ型は大きく2種類に分かれる
C#のデータ型は、大きく次の2種類に分けられます。
値型
参照型
両者の違いは、変数が値そのものを保持するか、オブジェクトへの参照を保持するかという点です。
2-1. 値型とは
値型は、変数が値そのものを保持する型です。
代表的な値型には、次のものがあります。
byteshortintlongfloatdoubledecimalcharboolstructenum
値型の変数を別の変数へ代入すると、基本的には値がコピーされます。
int number1 = 10;int number2 = number1;number2 = 20;
Console.WriteLine(number1); // 10Console.WriteLine(number2); // 20
number2を書き換えても、number1には影響しません。代入時に値がコピーされ、別々のデータとして扱われるためです。
2-2. 参照型とは
参照型は、オブジェクトそのものではなく、オブジェクトを参照するための情報を変数が保持する型です。
代表的な参照型には、次のものがあります。
string配列
classinterfacedelegateobjectdynamic
クラスのインスタンスを別の変数へ代入すると、通常は同じオブジェクトへの参照がコピーされます。
class User{public string Name { get; set; } = "";}User user1 = new User();user1.Name = "Taro";
User user2 = user1;user2.Name = "Hanako";
Console.WriteLine(user1.Name); // HanakoConsole.WriteLine(user2.Name); // Hanako
user1とuser2は同じUserオブジェクトを参照しています。そのため、user2を通じて変更した内容がuser1から見ても反映されます。
2-3. 値型と参照型の違い
値型と参照型の主な違いは、代入時の動作です。
int a = 10;int b = a;b = 50;値型では、aの値がbへコピーされます。bを変更してもaは変わりません。
一方、参照型では参照先が共有される場合があります。
int[] numbers1 = { 1, 2, 3 };int[] numbers2 = numbers1;numbers2[0] = 100;
Console.WriteLine(numbers1[0]); // 100
numbers1とnumbers2は同じ配列を参照しているため、片方から要素を変更すると、もう片方から見える内容も変わります。
ただし、stringは参照型でありながら、作成後に内容を変更できない「不変型」です。文字列を変更する操作を行うと、元のオブジェクトを書き換えるのではなく、新しい文字列が生成されます。
2-4. メモリ上での扱われ方の違い
値型と参照型は、「値そのものを保持するか」「オブジェクトへの参照を保持するか」が本質的な違いです。
初心者向けの説明では、値型はスタック、参照型の実体はヒープに保存されると説明されることがあります。しかし、実際の配置場所は変数がローカル変数か、クラスのフィールドか、配列の要素かなどによって変わります。
たとえば、値型のフィールドを持つクラスでは、値型のデータはクラスのオブジェクト内に含まれます。
class Product{public int Price { get; set; }}この例のPriceは値型ですが、Productオブジェクトの一部として管理されます。
そのため、単純に「値型は必ずスタック、参照型は必ずヒープ」と覚えるのではなく、次の違いを押さえることが重要です。
値型の変数は値そのものを表す
参照型の変数はオブジェクトへの参照を表す
値型の代入では値がコピーされる
参照型の代入では参照がコピーされる
2-5. nullを代入できる型とできない型
通常の参照型には、オブジェクトを参照していない状態を表すnullを代入できます。
string? message = null;int[]? numbers = null;User? user = null;通常の値型には、そのままではnullを代入できません。
int number = null; // エラー値型でnullを扱いたい場合は、nullable型を使用します。
int? number = null;bool? answer = null;int?はNullable<int>の省略形です。整数値に加えて、値が存在しないことを表すnullも保持できます。
3. C#の基本データ型一覧
3-1. 整数型:byte・short・int・long
C#で整数を扱う主な型は、byte、short、int、longです。
byte level = 10;short temperature = -100;int population = 1_000_000;long distance = 9_000_000_000L;各型の範囲は次のとおりです。
byte:0~255short:-32,768~32,767int:-2,147,483,648~2,147,483,647long:-9,223,372,036,854,775,808~9,223,372,036,854,775,807
byteは符号なし整数型ですが、小さな整数を扱う基本型として使われることがあります。通常の整数計算では、まずintを選ぶのが一般的です。
long型のリテラルには、必要に応じて末尾にLを付けます。
long largeNumber = 5_000_000_000L;3-2. 符号なし整数型:sbyte・ushort・uint・ulong
整数型には、正の値を中心に扱う符号なし型もあります。
sbyte signedByte = -100;ushort smallNumber = 60000;uint count = 4_000_000_000U;ulong hugeNumber = 18_000_000_000_000_000_000UL;各型の範囲は次のとおりです。
sbyte:-128~127ushort:0~65,535uint:0~4,294,967,295ulong:0~18,446,744,073,709,551,615
名称からも分かるとおり、sbyteは符号付き整数型です。符号なし整数型はbyte、ushort、uint、ulongです。
符号なし整数型は、負の値が存在しないデータやビット演算などで利用されます。ただし、一般的な業務アプリケーションでは、他のAPIとの互換性や計算のしやすさからintやlongを使う場面が多くあります。
3-3. 小数型:float・double・decimal
C#で小数を扱う代表的な型は、float、double、decimalです。
float rate = 1.5F;double average = 98.765;decimal price = 1980.50M;それぞれの特徴は次のとおりです。
| 型 | おおよその精度 | 主な用途 |
|---|---|---|
float | 6~9桁 | メモリ量や処理速度を重視する処理 |
double | 15~17桁 | 科学計算、座標、一般的な小数計算 |
decimal | 28~29桁 | 金額、会計、10進数の精度が重要な処理 |
floatのリテラルにはF、decimalのリテラルにはMを付けます。
float value1 = 1.23F;decimal value2 = 1.23M;floatとdoubleは2進浮動小数点数です。そのため、10進数の値を常に完全に表現できるとは限りません。
double result = 0.1 + 0.2;Console.WriteLine(result);金額のように10進数としての精度が重要なデータには、基本的にdecimalを使用します。
3-4. 文字型:char
char型は、1つのUTF-16コード単位を表す値型です。文字リテラルはシングルクォーテーションで囲みます。
char grade = 'A';char symbol = 'あ';char newLine = '\n';文字列を表すstringとは異なり、charには基本的に1つの文字を指定します。
char letter = 'A';string word = "ABC";一部のUnicode文字は複数のcharで表現されることがあります。そのため、charが常に人間に見える1文字と完全に一致するとは限りません。
3-5. 文字列型:string
string型は、文字列を扱う参照型です。文字列リテラルはダブルクォーテーションで囲みます。
string name = "Yamada";string message = "こんにちは";文字列は+演算子や文字列補間を使って組み立てられます。
string name = "Taro";int age = 20;string message1 = "名前:" + name + "、年齢:" + age;string message2 = $"名前:{name}、年齢:{age}";
一般的には、文字列補間を使うと読みやすいコードになります。
stringは参照型ですが、不変です。文字列を変更する処理では、新しいstringオブジェクトが生成されます。
3-6. 真偽値型:bool
bool型は、真偽を表す値型です。代入できる値はtrueまたはfalseです。
bool isActive = true;bool hasError = false;条件分岐でよく使われます。
bool isAdult = age >= 18;if (isAdult){Console.WriteLine("成人です");}
C#では、整数の0や1をboolとして直接扱うことはできません。
bool flag = 1; // エラー3-7. object型
object型は、C#におけるすべての型の基底となる型です。値型と参照型の両方を代入できます。
object value1 = 100;object value2 = "Hello";object value3 = true;値型をobject型へ代入すると、ボックス化が行われます。
int number = 100;object boxed = number;objectから元の値型へ戻すときは、アンボックス化が必要です。
int restored = (int)boxed;実際の型と異なる型へキャストすると、実行時にInvalidCastExceptionが発生します。
object value = "100";int number = (int)value; // 実行時エラー文字列を整数へ変換したい場合は、キャストではなくint.Parseやint.TryParseを使用します。
3-8. dynamic型
dynamic型を使用すると、コンパイル時の型チェックの一部が実行時まで延期されます。
dynamic value = "Hello";Console.WriteLine(value.Length);value = 100;Console.WriteLine(value + 50);
dynamic型の変数には、さまざまな型の値を代入できます。ただし、存在しないメソッドやプロパティを呼び出しても、コンパイル時には検出されない場合があります。
dynamic value = 100;value.NotExists(); // 実行時にエラーdynamicは、動的なデータを扱うライブラリや相互運用機能などで便利です。一方、型安全性が低下するため、通常のコードでは必要な場面に限定して使用するのが適切です。
3-9. データ型一覧表
C#の代表的なデータ型をまとめると、次のようになります。
| C#の型 | .NETの型 | 分類 | 内容 |
sbyte | System.SByte | 値型 | 8ビット符号付き整数 |
byte | System.Byte | 値型 | 8ビット符号なし整数 |
short | System.Int16 | 値型 | 16ビット符号付き整数 |
ushort | System.UInt16 | 値型 | 16ビット符号なし整数 |
int | System.Int32 | 値型 | 32ビット符号付き整数 |
uint | System.UInt32 | 値型 | 32ビット符号なし整数 |
long | System.Int64 | 値型 | 64ビット符号付き整数 |
ulong | System.UInt64 | 値型 | 64ビット符号なし整数 |
float | System.Single | 値型 | 単精度浮動小数点数 |
double | System.Double | 値型 | 倍精度浮動小数点数 |
decimal | System.Decimal | 値型 | 高精度の10進数 |
char | System.Char | 値型 | UTF-16コード単位 |
bool | System.Boolean | 値型 | 真偽値 |
string | System.String | 参照型 | 文字列 |
object | System.Object | 参照型 | すべての型の基底型 |
dynamic | 実行時に決定 | 特殊 | 型チェックを実行時に延期 |
intとSystem.Int32、stringとSystem.Stringは、それぞれ同じ型を表します。
int number1 = 10;System.Int32 number2 = 20;string text1 = "A";System.String text2 = "B";
通常は、簡潔で読みやすいC#のキーワードであるintやstringを使います。
4. 値型の代表例と使い方
4-1. int型の使い方
int型は、整数を扱うときの基本となるデータ型です。
int age = 25;int quantity = 3;int price = 1200;int total = quantity * price;
Console.WriteLine(total); // 3600
int同士の除算では、小数部分が切り捨てられます。
int result = 5 / 2;Console.WriteLine(result); // 2小数まで求めたい場合は、少なくとも一方を小数型にします。
double result = 5.0 / 2;Console.WriteLine(result); // 2.5intの範囲を超える計算では、オーバーフローに注意が必要です。
int max = int.MaxValue;Console.WriteLine(max); // 2147483647オーバーフローを検出したい場合は、checkedを利用できます。
checked{int max = int.MaxValue;int result = max + 1;}4-2. double型・decimal型の使い分け
doubleは、一般的な小数計算や科学技術計算などに適しています。
double width = 10.5;double height = 4.2;double area = width * height;decimalは、金額や会計など10進数としての精度が重要な計算に適しています。
decimal unitPrice = 1980.50M;int quantity = 3;decimal totalPrice = unitPrice * quantity;使い分けの基本は次のとおりです。
測定値、座標、統計、科学計算:
double金額、税率、会計計算:
decimal
doubleとdecimalは直接計算できません。
double value1 = 1.5;decimal value2 = 2.0M;// var result = value1 + value2; // エラー
計算する場合は、どちらかの型へ明示的に変換します。
decimal result = (decimal)value1 + value2;ただし、変換時には精度や扱える範囲を考慮する必要があります。
4-3. bool型の使い方
bool型は、条件を表す変数に使います。
int age = 20;bool isAdult = age >= 18;if (isAdult){Console.WriteLine("成人です");}else{Console.WriteLine("未成年です");}
変数名は、真偽が分かりやすい形にすると可読性が高まります。
bool isEnabled = true;bool hasPermission = false;bool canEdit = true;is、has、canなどから始めると、trueが何を意味するのか伝わりやすくなります。
4-4. char型の使い方
char型は、1つの文字や記号を扱うときに使います。
char initial = 'T';char separator = ',';char tab = '\t';文字列から特定の位置のcharを取得することもできます。
string text = "CSharp";char firstCharacter = text[0];Console.WriteLine(firstCharacter); // C
char型かどうかを調べるための便利なメソッドもあります。
char value = '5';Console.WriteLine(char.IsDigit(value)); // TrueConsole.WriteLine(char.IsLetter(value)); // False
4-5. struct型とは
structは、複数のデータをまとめて独自の値型を定義するための仕組みです。
public struct Point{public int X { get; set; }public int Y { get; set; }public Point(int x, int y){X = x;Y = y;}
}
次のように使用できます。
Point point1 = new Point(10, 20);Point point2 = point1;point2.X = 100;
Console.WriteLine(point1.X); // 10Console.WriteLine(point2.X); // 100
Pointは値型なので、代入時に値がコピーされます。
structは、小さく単純で、値として扱うのが自然なデータに適しています。座標、日付、色、範囲などが代表例です。
大きな構造体を頻繁にコピーすると、処理コストが高くなる可能性があります。継承を利用したい場合や、同じインスタンスを複数箇所で共有したい場合は、classが適しています。
4-6. enum型とは
enumは、関連する定数をひとまとめにして表現する値型です。
public enum OrderStatus{Pending,Processing,Shipped,Completed,Canceled}次のように使用します。
OrderStatus status = OrderStatus.Processing;if (status == OrderStatus.Processing){Console.WriteLine("処理中です");}
数値だけで状態を表すよりも、enumを使用したほうが意味を理解しやすくなります。
int status = 1; // 1が何を意味するのか分かりにくいOrderStatus status = OrderStatus.Processing;列挙値には、明示的な数値を割り当てることもできます。
public enum HttpStatus{Ok = 200,NotFound = 404,InternalServerError = 500}5. 参照型の代表例と使い方
5-1. string型の特徴
stringは、複数の文字を扱う参照型です。
string title = "C#入門";stringには、文字列を操作する多くのメソッドがあります。
string text = "CSharp Programming";Console.WriteLine(text.Length);Console.WriteLine(text.ToUpper());Console.WriteLine(text.Contains("Sharp"));Console.WriteLine(text.Replace("CSharp", "C#"));
stringは不変型なので、Replaceなどを呼び出しても元の文字列自体は変わりません。
string text = "Hello";text.Replace("H", "Y");Console.WriteLine(text); // Hello
結果を利用するには、戻り値を変数へ代入します。
text = text.Replace("H", "Y");Console.WriteLine(text); // Yello
文字列を大量に連結する場合は、StringBuilderの利用も検討します。
using System.Text;StringBuilder builder = new StringBuilder();
for (int i = 0; i < 1000; i++){builder.Append(i);}
string result = builder.ToString();
5-2. 配列型の使い方
配列は、同じ型のデータを複数まとめて管理する参照型です。
int[] scores = { 80, 90, 75 };インデックスは0から始まります。
Console.WriteLine(scores[0]); // 80Console.WriteLine(scores[1]); // 90要素を書き換えることもできます。
scores[2] = 100;配列の長さはLengthプロパティで取得できます。
Console.WriteLine(scores.Length); // 3foreachを使うと、すべての要素を順番に処理できます。
foreach (int score in scores){Console.WriteLine(score);}配列は作成後に長さを変更できません。要素数が増減するデータには、List<T>がよく使われます。
List<string> names = new List<string>();names.Add("Taro");names.Add("Hanako");5-3. class型とは
classは、データと処理をまとめて独自の参照型を定義する仕組みです。
public class Product{public string Name { get; set; } = "";public decimal Price { get; set; }public decimal CalculateTaxIncludedPrice(decimal taxRate){return Price * (1 + taxRate);}
}
インスタンスはnewを使って作成します。
Product product = new Product{Name = "キーボード",Price = 5000M};decimal taxIncludedPrice = product.CalculateTaxIncludedPrice(0.1M);
クラスは、次のようなデータに適しています。
複数の場所から同じインスタンスを共有する
継承を利用する
状態と処理をまとめる
大きく複雑なデータを表現する
5-4. interface型とは
interfaceは、クラスや構造体が実装すべき機能の契約を定義する参照型です。
public interface ILogger{void Log(string message);}インターフェースを実装するクラスは、定義されたメンバーを実装します。
public class ConsoleLogger : ILogger{public void Log(string message){Console.WriteLine(message);}}インターフェース型の変数には、そのインターフェースを実装したオブジェクトを代入できます。
ILogger logger = new ConsoleLogger();logger.Log("処理を開始しました");インターフェースを使うと、具体的な実装に強く依存しないコードを書きやすくなります。
5-5. delegate型とは
delegateは、メソッドを参照するための型です。処理そのものを変数に代入したり、別のメソッドへ渡したりできます。
public delegate int Calculation(int x, int y);デリゲートへメソッドを代入します。
static int Add(int x, int y){return x + y;}Calculation calculation = Add;int result = calculation(10, 20);
Console.WriteLine(result); // 30
実際の開発では、組み込みのActionやFuncがよく使われます。
Action<string> print = message => Console.WriteLine(message);print("Hello");Func<int, int, int> add = (x, y) => x + y;Console.WriteLine(add(3, 5));
デリゲートは、イベント処理、コールバック、LINQなどで利用されます。
5-6. object型との関係
C#のすべての型は、最終的にobject型の機能を利用できます。
int number = 100;string text = "Hello";Console.WriteLine(number.ToString());Console.WriteLine(text.ToString());
object型には、次のような基本メソッドがあります。
ToStringEqualsGetHashCodeGetType
object型の変数へさまざまなデータを格納できますが、利用時には元の型を確認する必要があります。
object value = "C#";if (value is string text){Console.WriteLine(text.Length);}
型が分からないデータを安全に処理するには、パターンマッチングが便利です。
object value = 100;switch (value){case int number:Console.WriteLine($"整数:{number}");break;
case string text:Console.WriteLine($"文字列:{text}");break;default:Console.WriteLine("その他の型");break;
}
6. C#のデータ型の宣言方法
6-1. 明示的に型を指定する方法
変数を宣言するときは、型名、変数名、値の順に記述します。
int age = 20;string name = "Taro";bool isActive = true;decimal price = 1500M;明示的に型を指定すると、変数がどのデータを扱うのか一目で分かります。
値を後から代入することもできます。
int score;score = 80;ただし、ローカル変数は値を代入する前に読み取れません。
int score;// Console.WriteLine(score); // エラー6-2. varを使って型推論する方法
varを使うと、右辺の値からコンパイラが型を推論します。
var age = 20; // intvar name = "Taro"; // stringvar price = 1500.0M; // decimalvarで宣言しても、変数の型がなくなるわけではありません。コンパイル時に具体的な型が決まります。
var number = 100;// number = "Hello"; // エラーnumberはint型として推論されているため、後からstring型の値を代入することはできません。
varは、右辺から型が明らかな場合に便利です。
var product = new Product();var names = new List<string>();6-3. constとreadonlyの違い
constは、コンパイル時に値が決まる定数を宣言するために使います。
const double Pi = 3.141592653589793;const int MaxCount = 100;constで宣言した値は変更できません。
const int MaxCount = 100;// MaxCount = 200; // エラーreadonlyは、フィールドの値を宣言時またはコンストラクター内で設定し、その後の変更を禁止します。
public class AppSettings{public readonly string ApplicationName;public AppSettings(string applicationName){ApplicationName = applicationName;}
}
主な違いは次のとおりです。
| 項目 | const | readonly |
| 値が決まる時期 | コンパイル時 | 実行時でもよい |
| 使用場所 | ローカル変数、フィールド | フィールド |
| コンストラクターで代入 | できない | できる |
| 参照型 | 原則としてnullや文字列に限定 | 任意の型を使用可能 |
暗黙のstatic | フィールドではstatic扱い | 必要なら明示する |
6-4. 変数宣言時の初期化
変数は、可能な限り宣言と同時に初期化すると安全です。
int count = 0;string message = "";bool isCompleted = false;クラスのプロパティでも、初期値を設定できます。
public class User{public string Name { get; set; } = "";public int Age { get; set; } = 0;}コレクションも宣言時に初期化すると、nullによるエラーを防ぎやすくなります。
public class Team{public List<string> Members { get; set; } = new List<string>();}6-5. 型推論を使うときの注意点
varは便利ですが、右辺を見ても型が分かりにくい場合は、可読性が下がることがあります。
var result = GetData();GetDataの戻り値がコード上で分かりにくい場合、明示的な型名を使う選択肢があります。
List<Product> result = GetData();一方、型名を重複して書く必要がない場面では、varが読みやすいこともあります。
var products = new List<Product>();型推論を使うかどうかは、単純なルールで決めるのではなく、コードを読む人が型を理解しやすいかどうかで判断します。
また、varには初期値が必要です。
// var value; // エラー// var value = null; // 型を推論できないためエラー型を指定すれば、nullで初期化できます。
string? value = null;7. C#の型変換の基本
7-1. 暗黙的な型変換
データを失う可能性が低い変換は、暗黙的に行われる場合があります。
int number = 100;long largeNumber = number;intの値はすべてlongの範囲に収まるため、明示的なキャストは不要です。
float value1 = 1.5F;double value2 = value1;ただし、変換先の型で元の値を完全に表現できるとは限らない組み合わせもあります。数値型の変換では、値の範囲だけでなく精度にも注意が必要です。
7-2. 明示的な型変換
大きな範囲の型から小さな範囲の型へ変換する場合など、データが失われる可能性がある変換には明示的な指定が必要です。
long largeNumber = 100;int number = (int)largeNumber;小数型を整数型へ変換すると、小数部分は切り捨てられます。
double value = 12.9;int number = (int)value;Console.WriteLine(number); // 12
四捨五入したい場合は、Math.Roundなどを使用します。
double value = 12.9;int number = (int)Math.Round(value);Console.WriteLine(number); // 13
7-3. キャストを使った変換
キャストは、値の前に変換先の型を丸括弧で指定します。
double value = 10.5;int number = (int)value;参照型でもキャストを使えます。
object value = "Hello";string text = (string)value;ただし、実際の型が一致しないと実行時エラーになります。
object value = 100;// string text = (string)value; // InvalidCastException安全に型を確認するには、isパターンを使用します。
object value = "Hello";if (value is string text){Console.WriteLine(text.Length);}
as演算子を使う方法もあります。
object value = "Hello";string? text = value as string;if (text != null){Console.WriteLine(text.Length);}
asによる変換に失敗するとnullになります。主に参照型やnullable型で使用します。
7-4. Convertクラスを使った変換
Convertクラスには、基本型を変換するためのメソッドが用意されています。
string text = "123";int number = Convert.ToInt32(text);double value = Convert.ToDouble("12.5");bool flag = Convert.ToBoolean("true");
Convertは、文字列以外の値を変換するときにも使えます。
double value = 12.8;int number = Convert.ToInt32(value);Convert.ToInt32で浮動小数点数を整数へ変換すると、単純なキャストとは丸め方が異なります。
double value = 12.8;int castResult = (int)value;int convertResult = Convert.ToInt32(value);
Console.WriteLine(castResult); // 12Console.WriteLine(convertResult); // 13
また、Convert.ToInt32(null)は0を返します。一方、int.Parse(null)は例外になります。変換方法によってnullや端数の扱いが異なるため、目的に合わせて選ぶ必要があります。
7-5. Parseメソッドを使った変換
Parseメソッドは、文字列を特定の型へ変換します。
int number = int.Parse("123");double value = double.Parse("12.5");decimal price = decimal.Parse("1980.50");bool flag = bool.Parse("true");変換できない文字列を渡すと、例外が発生します。
int number = int.Parse("ABC"); // FormatException値が型の範囲を超えている場合は、OverflowExceptionが発生します。
外部から受け取った入力のように、正しい形式か分からないデータには、ParseよりTryParseが適しています。
7-6. TryParseメソッドを使った安全な変換
TryParseは、変換に成功したかどうかをboolで返します。変換に失敗しても、通常は例外を発生させません。
string input = "123";bool success = int.TryParse(input, out int number);
if (success){Console.WriteLine($"変換成功:{number}");}else{Console.WriteLine("整数へ変換できません");}
条件式の中に直接記述することもできます。
if (int.TryParse(input, out int number)){Console.WriteLine(number);}else{Console.WriteLine("正しい整数を入力してください");}ユーザー入力、CSV、外部APIなど、形式が保証されていないデータを変換するときはTryParseを優先すると安全です。
7-7. 型変換で発生しやすいエラー
型変換では、主に次の問題が発生します。
FormatException
文字列の形式が変換先の型に合わない場合に発生します。
int number = int.Parse("12A");OverflowException
値が変換先の型の範囲を超えた場合に発生します。
byte value = byte.Parse("1000");InvalidCastException
実際の型と互換性のない型へキャストした場合に発生します。
object value = "100";int number = (int)value;小数部分や精度の消失
明示的なキャストが成功しても、データが失われることがあります。
double value = 10.99;int number = (int)value;Console.WriteLine(number); // 10
オーバーフロー
数値型の範囲を超える変換や計算では、意図しない値になる可能性があります。
int number = int.MaxValue;unchecked{number++;}
Console.WriteLine(number); // int.MinValue
変換元のデータが信頼できない場合は、TryParse、範囲チェック、checkedなどを組み合わせて対策します。
8. nullable型とnullの扱い
8-1. nullable型とは
nullable型は、通常の値に加えてnullを保持できる型です。
通常の値型にはnullを代入できません。
int age = null; // エラーnullable型にすれば、値が存在しない状態を表現できます。
int? age = null;データベースの未入力項目や、回答されていないアンケートなど、値が存在しない状態に意味がある場合に利用します。
8-2. int?などの書き方
型名の後ろに?を付けると、nullable値型になります。
int? age = null;double? score = null;bool? answer = null;DateTime? completedAt = null;int?は、次の書き方と同じ意味です。
Nullable<int> age = null;値が存在するかどうかは、HasValueで確認できます。
int? age = 20;if (age.HasValue){Console.WriteLine(age.Value);}
通常は、パターンマッチングを使うと簡潔に書けます。
if (age is int value){Console.WriteLine(value);}8-3. null合体演算子の使い方
null合体演算子??を使うと、値がnullの場合の代替値を指定できます。
int? inputAge = null;int age = inputAge ?? 0;Console.WriteLine(age); // 0
文字列でも利用できます。
string? name = null;string displayName = name ?? "名前未設定";??=を使うと、変数がnullの場合だけ値を代入できます。
string? message = null;message ??= "初期メッセージ";Console.WriteLine(message);
null条件演算子?.を使うと、オブジェクトがnullでない場合だけメンバーへアクセスできます。
string? text = null;int? length = text?.Length;代替値と組み合わせることもできます。
int length = text?.Length ?? 0;8-4. null許容参照型とは
null許容参照型は、参照型がnullになる可能性をコード上で明確にする仕組みです。
string name = "Taro";string? middleName = null;stringは原則としてnullにしない変数、string?はnullになる可能性がある変数として扱われます。
string? message = GetMessage();Console.WriteLine(message.Length);このようなコードでは、messageがnullかもしれないため、コンパイラが警告を出します。
nullチェックを行えば、安全に利用できます。
if (message != null){Console.WriteLine(message.Length);}null条件演算子を使うこともできます。
Console.WriteLine(message?.Length ?? 0);null許容参照型は、実行時に新しい参照型を作る機能ではありません。コンパイラによる静的解析を利用して、nullに関する問題を見つけやすくする仕組みです。
8-5. NullReferenceExceptionを防ぐ考え方
NullReferenceExceptionは、nullの参照からプロパティやメソッドへアクセスしたときに発生します。
string? name = null;Console.WriteLine(name.Length);防止する方法には、次のようなものがあります。
宣言時に初期化する
string name = "";List<string> names = new List<string>();nullチェックを行う
if (name != null){Console.WriteLine(name.Length);}null条件演算子を使う
int length = name?.Length ?? 0;必須データをコンストラクターで受け取る
public class User{public string Name { get; }public User(string name){Name = name ?? throw new ArgumentNullException(nameof(name));}
}
null許容参照型を有効にする
stringとstring?を使い分けることで、nullになる可能性をコンパイル時に確認しやすくなります。
nullを後から場当たり的に処理するのではなく、「このデータは本当に未設定になり得るのか」を設計段階で考えることが重要です。
9. C#のデータ型を選ぶときのポイント
9-1. 整数を扱うならintを基本にする
一般的な整数には、まずintを使用します。
int age = 30;int count = 100;int score = 85;byteやshortを使えば変数単体のサイズは小さくなりますが、通常の整数演算ではintが標準的です。小さな範囲の値だからという理由だけで、必ずしも小さな型を選ぶ必要はありません。
次のような場合は別の型を検討します。
intの上限を超える可能性がある:longバイナリデータを扱う:
byte外部仕様で型が決められている:仕様に合わせる
ビット演算や相互運用で符号なし値が必要:
uintなど
9-2. 小数を扱うならdouble・decimalを使い分ける
小数だから常に同じ型を選ぶわけではありません。
double temperature = 23.5;decimal price = 1280.50M;目安は次のとおりです。
測定値や科学計算:
double金額や会計:
decimalメモリ使用量を抑えたい画像・ゲーム処理など:
float
decimalは高精度ですが、doubleより扱える指数範囲が狭く、一般に演算コストも高くなります。必要な精度と用途に合わせて選択します。
9-3. 文字列はstringを使う
名前、住所、商品コード、メッセージなど、複数の文字を扱うデータにはstringを使用します。
string userName = "Taro";string address = "東京都";string productCode = "A-001";数字だけで構成されていても、計算しない識別子はstringが適している場合があります。
string postalCode = "0010001";string phoneNumber = "09012345678";郵便番号や電話番号を数値型にすると、先頭の0が失われたり、ハイフンを扱えなかったりします。
9-4. 金額計算ではdecimalを使う
金額には、基本的にdecimalを使用します。
decimal price = 1980M;decimal taxRate = 0.1M;decimal taxIncludedPrice = price * (1 + taxRate);doubleでは、2進浮動小数点数の性質によって、10進小数を正確に表現できない場合があります。
double result = 0.1 + 0.2;金額計算では、型だけでなく端数処理のルールも重要です。
decimal price = 1050M;decimal taxRate = 0.1M;decimal tax = Math.Floor(price * taxRate);decimal total = price + tax;
切り捨て、切り上げ、四捨五入のどれを使うかは、業務要件に合わせて決めます。
9-5. 大きな数値を扱うときの注意点
intの範囲を超える整数にはlongを使用します。
long population = 8_000_000_000L;longでも収まらない整数を扱う場合は、System.Numerics.BigIntegerを利用できます。
using System.Numerics;BigInteger hugeNumber = BigInteger.Parse("123456789012345678901234567890");
ただし、型の範囲内でも計算結果がオーバーフローする可能性があります。
int width = 100000;int height = 100000;long area = (long)width * height;
次の書き方では、int同士の乗算が先に行われるため、longへ代入する前にオーバーフローする可能性があります。
long area = width * height;計算前に片方をlongへ変換することがポイントです。
9-6. 可読性と保守性を意識して型を選ぶ
データ型は、値を保存できれば何でもよいわけではありません。データの意味が伝わる型を選ぶことが重要です。
たとえば、注文状態をintで表すより、enumを使うほうが意図を理解しやすくなります。
int status = 2;OrderStatus status = OrderStatus.Shipped;日時を文字列で保存するより、DateTimeやDateTimeOffsetを使うほうが、比較や計算を安全に行えます。
DateTime createdAt = DateTime.Now;同様に、次のような選択を意識します。
日付や時刻:
DateTime、DateTimeOffset期間:
TimeSpan一意な識別子:
Guid状態の選択肢:
enum複数の同種データ:配列、
List<T>金額:
decimal
適切な型を選ぶと、コードの意味が明確になり、不正な値も入りにくくなります。
10. C#のデータ型でよくある疑問
10-1. intとlongの違い
intとlongは、扱える整数の範囲が異なります。
| 型 | サイズ | 範囲 |
int | 32ビット | 約-21億~21億 |
long | 64ビット | 約-922京~922京 |
一般的な整数はint、intの範囲を超える可能性がある値にはlongを使います。
int userCount = 100000;long fileSize = 5_000_000_000L;longからintへの変換には、明示的なキャストが必要です。
long value = 100;int number = (int)value;変換元がintの範囲を超えている可能性がある場合は、事前に確認します。
if (value >= int.MinValue && value <= int.MaxValue){int number = (int)value;}10-2. floatとdoubleとdecimalの違い
3つの型は、精度、範囲、内部表現、主な用途が異なります。
| 型 | 特徴 | 主な用途 |
float | 精度が比較的低く、サイズが小さい | グラフィックス、ゲームなど |
double | 広い範囲と十分な精度 | 一般的な小数、科学計算 |
decimal | 10進数として高精度 | 金額、会計 |
float x = 1.5F;double y = 1.5;decimal z = 1.5M;特別な理由がない一般的な小数計算ではdouble、金額ではdecimalを選ぶのが基本です。
10-3. stringとcharの違い
charは1つのUTF-16コード単位、stringは複数の文字からなる文字列を表します。
char grade = 'A';string gradeText = "A";リテラルを囲む記号も異なります。
char:シングルクォーテーションstring:ダブルクォーテーション
char symbol = '#';string text = "C#";また、charは値型、stringは参照型です。
10-4. objectとdynamicの違い
objectとdynamicは、どちらもさまざまな型の値を格納できます。
object objectValue = "Hello";dynamic dynamicValue = "Hello";違いは、メンバーへのアクセスをいつ確認するかです。
objectでは、文字列のメンバーを使う前に型の確認や変換が必要です。
object value = "Hello";if (value is string text){Console.WriteLine(text.Length);}
dynamicでは、実行時に型が確認されます。
dynamic value = "Hello";Console.WriteLine(value.Length);存在しないメンバーを呼び出した場合、objectではコンパイル時に検出しやすい一方、dynamicでは実行時エラーになる可能性があります。
型安全性を重視する通常のコードでは、objectとパターンマッチング、またはジェネリック型を使うのが基本です。
10-5. varは型を指定しない書き方なのか
varは、型をなくす書き方ではありません。右辺からコンパイラが型を推論する書き方です。
var count = 10;このcountは、コンパイル時にint型として決まります。
// count = "10"; // エラー実行中に型が自由に変わるわけではありません。型を実行時に解決するdynamicとは異なります。
dynamic value = 10;value = "10"; // 可能10-6. 値型と参照型はどちらを使うべきか
どちらが優れているというものではなく、データの性質に合わせて選びます。
値型が適している例は次のとおりです。
数値や真偽値など単純な値
小さく、値として比較したいデータ
独立したコピーとして扱いたいデータ
参照型が適している例は次のとおりです。
複数箇所で同じインスタンスを共有したい
継承を利用したい
状態や処理を多く持つ複雑なデータ
オブジェクトの同一性が重要
独自型を作る場合も、単にパフォーマンスだけで決めるのではなく、そのデータを「値」として扱うのか「同一性を持つオブジェクト」として扱うのかを考えます。
11. C#のデータ型を学ぶためのサンプルコード
11-1. 基本型を使った変数宣言の例
int age = 25;long population = 8_000_000_000L;float height = 170.5F;double average = 82.75;decimal price = 1980.50M;char grade = 'A';string name = "Taro";bool isActive = true;object value = "任意のデータ";Console.WriteLine($"名前:{name}");Console.WriteLine($"年齢:{age}");Console.WriteLine($"身長:{height}");Console.WriteLine($"平均点:{average}");Console.WriteLine($"価格:{price}");Console.WriteLine($"評価:{grade}");Console.WriteLine($"有効:{isActive}");Console.WriteLine($"object:{value}");
数値リテラルには、読みやすくするためにアンダースコアを挿入できます。
int oneMillion = 1_000_000;long largeNumber = 10_000_000_000L;アンダースコアは数値に影響しません。
11-2. 値型と参照型の動作を比較する例
値型では、代入時に値がコピーされます。
int value1 = 10;int value2 = value1;value2 = 99;
Console.WriteLine(value1); // 10Console.WriteLine(value2); // 99
参照型では、代入によって同じオブジェクトへの参照が共有されることがあります。
public class Person{public string Name { get; set; } = "";}Person person1 = new Person{Name = "Taro"};
Person person2 = person1;person2.Name = "Hanako";
Console.WriteLine(person1.Name); // HanakoConsole.WriteLine(person2.Name); // Hanako
別のオブジェクトとして複製したい場合は、新しいインスタンスを作成します。
Person person2 = new Person{Name = person1.Name};11-3. 型変換のサンプルコード
// 暗黙的な変換int intValue = 100;long longValue = intValue;// 明示的な変換double doubleValue = 12.8;int castValue = (int)doubleValue;
// Convertを使う変換string numberText = "200";int convertedValue = Convert.ToInt32(numberText);
// Parseを使う変換decimal price = decimal.Parse("1980.50");
Console.WriteLine(longValue); // 100Console.WriteLine(castValue); // 12Console.WriteLine(convertedValue); // 200Console.WriteLine(price); // 1980.50
文字列を数値へ変換するときは、利用環境のカルチャによって小数点や桁区切りの解釈が変わることがあります。外部データの形式が決まっている場合は、必要に応じてカルチャを明示します。
using System.Globalization;decimal price = decimal.Parse("1980.50",CultureInfo.InvariantCulture);
11-4. TryParseを使った入力チェックの例
Console.Write("年齢を入力してください:");string? input = Console.ReadLine();if (int.TryParse(input, out int age)){if (age >= 0){Console.WriteLine($"入力された年齢は{age}歳です");}else{Console.WriteLine("年齢には0以上の数値を入力してください");}}else{Console.WriteLine("整数を入力してください");}
TryParseが成功しても、業務上正しい値とは限りません。そのため、型変換後に範囲も確認することが大切です。
if (int.TryParse(input, out int score) &&score >= 0 &&score <= 100){Console.WriteLine($"点数:{score}");}else{Console.WriteLine("0~100の整数を入力してください");}11-5. nullable型を使ったサンプルコード
int? score = null;if (score.HasValue){Console.WriteLine($"点数:{score.Value}");}else{Console.WriteLine("点数は未入力です");}
null合体演算子を使うと、代替値を簡潔に指定できます。
int displayScore = score ?? 0;Console.WriteLine(displayScore);null許容参照型と組み合わせた例は次のとおりです。
string? userName = null;string displayName = userName ?? "ゲスト";int nameLength = userName?.Length ?? 0;
Console.WriteLine($"表示名:{displayName}");Console.WriteLine($"文字数:{nameLength}");
メソッドの戻り値が存在しない可能性を表す場合にも使用できます。
static int? FindScore(string userName){if (userName == "Taro"){return 90;}return null;
}
int? score = FindScore("Hanako");
Console.WriteLine(score?.ToString() ?? "データなし");
まとめ
C#のデータ型は、大きく値型と参照型に分けられます。
int、double、decimal、bool、char、struct、enumなどは値型です。値型を別の変数へ代入すると、基本的に値がコピーされます。
string、配列、class、interface、delegate、objectなどは参照型です。参照型を代入すると、同じオブジェクトへの参照が共有される場合があります。
データ型を選ぶときは、次の考え方を基本にするとよいでしょう。
一般的な整数には
intintを超える整数にはlong一般的な小数計算には
double金額や会計計算には
decimal1つの文字には
char文字列には
string真偽を表す値には
bool値が存在しない可能性がある値型には
int?などのnullable型外部入力の変換には
TryParse
また、varを使っても型がなくなるわけではありません。型はコンパイル時に決まり、その後に別の型へ自由に変更することはできません。
型変換では、暗黙的な変換、キャスト、Convert、Parse、TryParseを目的に応じて使い分けます。特にユーザー入力や外部データには、例外を発生させずに変換結果を確認できるTryParseが便利です。
C#のデータ型を理解することは、変数宣言、条件分岐、計算、クラス設計、データベース処理など、あらゆるプログラミングの基礎になります。最初からすべての型を暗記する必要はありません。まずはint、double、decimal、string、bool、nullable型の特徴を理解し、サンプルコードを実行しながら値型と参照型の違いを身につけることが大切です。

