C# Types(型)完全ガイド:値型・参照型・型変換・Nullableを初心者向けに解説
1. C# Types(型)とは?初心者が最初に理解すべき基本
1-1. C#における「型」の役割
型とは、データの種類と、そのデータに対して実行できる操作を定義する仕組みです。
たとえば、整数を保存するint、文字列を保存するstring、真偽値を保存するboolでは、扱える値や利用できる演算が異なります。
int age = 25;string name = "田中";bool isActive = true;ageには整数、nameには文字列、isActiveにはtrueまたはfalseを代入できます。型に合わない値を代入しようとすると、通常はコンパイルエラーになります。
int age = "25"; // コンパイルエラー1-2. なぜC#では型の理解が重要なのか
型を理解すると、次のようなメリットがあります。
不正なデータの代入を防げる
プログラムの意図を明確にできる
実行前に多くの間違いを検出できる
適切なメモリ使用や処理速度を意識できる
nullや型変換に関するエラーを減らせる
たとえば、年齢を表す変数をintとして宣言すれば、誤って名前や住所を代入することはできません。
int age = 30;age = "三十歳"; // コンパイルエラー型は単なる記法ではなく、プログラムの安全性を支える重要な仕組みです。
1-3. 静的型付け言語としてのC#の特徴
C#は静的型付け言語です。変数や式の型が、原則としてコンパイル時に決定されます。
int count = 10;count = 20;count = "20"; // コンパイルエラー実行前に型の不整合を検出できるため、実行時エラーを減らせます。また、IDEによる入力補完やリファクタリングも型情報を利用して行われます。
varを使用した場合も、C#が動的型付けになるわけではありません。
var count = 10;このコードでは、コンパイラが右辺からcountをint型だと推論します。一度決まった型を後から変更することはできません。
1-4. 「csharp types」で検索する人が知りたい内容の全体像
「csharp types」と検索する人が理解しておきたい主な内容は、次のとおりです。
値型と参照型の違い
intやstringなどの組み込み型class、struct、enumなどのユーザー定義型暗黙的変換、キャスト、
Parseなどの型変換Nullable型とnull許容参照型
var、dynamic、objectの違いジェネリック型の基本
型を安全に使い分ける方法
これらは個別の知識ではなく、C#の型システムとして相互に関係しています。
2. C#の型の全体像:値型・参照型・ジェネリック型・Nullable
2-1. C#の主な型分類
C#の型は、大きく次の2種類に分類できます。
値型(Value Types)
参照型(Reference Types)
値型には、整数型、浮動小数点型、bool、char、struct、enumなどがあります。
参照型には、class、string、配列、delegate、interfaceなどがあります。
そのほか、既存の型を組み合わせる仕組みとして、次のような型があります。
Nullable値型
null許容参照型
ジェネリック型
タプル型
配列型
ポインター型
2-2. 値型と参照型の違い
値型の変数には、基本的に値そのものが格納されます。一方、参照型の変数には、オブジェクトを参照するための情報が格納されます。
int a = 10;int b = a;b = 20;
Console.WriteLine(a); // 10Console.WriteLine(b); // 20
値型を代入すると値がコピーされるため、bを変更してもaには影響しません。
Person person1 = new Person();person1.Name = "田中";Person person2 = person1;person2.Name = "佐藤";
Console.WriteLine(person1.Name); // 佐藤
参照型を代入すると、通常は同じオブジェクトを指す参照がコピーされます。そのため、person2経由の変更がperson1からも確認できます。
2-3. 組み込み型とユーザー定義型
C#には、言語が標準で提供する組み込み型があります。
int number = 100;double rate = 1.5;bool enabled = true;string message = "Hello";一方、開発者自身が定義する型をユーザー定義型と呼びます。
public class Product{public string Name { get; set; } = "";public decimal Price { get; set; }}Product型を定義すると、商品名と価格をひとまとまりのデータとして扱えます。
2-4. 型システムを理解するための基本用語
C# Typesを理解する際は、次の用語を区別することが重要です。
変数は、値や参照を保持するための名前付き領域です。
インスタンスは、型の定義をもとに生成された具体的なデータです。
オブジェクトは、一般に型のインスタンスを指します。
宣言時の型は、変数を宣言したときに指定された型です。
Animal animal = new Dog();この場合、変数animalの宣言時の型はAnimal、実際に生成されたインスタンスの実行時型はDogです。
3. 値型(Value Types)とは
3-1. 値型の基本概念
値型は、データを値として扱う型です。ある値型変数を別の変数へ代入すると、通常は値のコピーが作られます。
int original = 10;int copied = original;copied = 99;
Console.WriteLine(original); // 10
値型は暗黙的にSystem.ValueTypeを継承し、最終的にはobject型の体系に含まれます。
3-2. int・double・bool・charなどの基本的な値型
代表的な値型には次のものがあります。
int count = 100;double temperature = 36.5;decimal price = 1980.50m;bool isCompleted = false;char initial = 'A';intは整数、doubleは小数、decimalは高精度な10進数、boolは真偽値、charは1つのUTF-16コード単位を表します。
なお、C#の組み込み型名は.NET型の別名です。
int number1 = 10;System.Int32 number2 = 10;intとSystem.Int32は同じ型です。
3-3. 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は値型なので、代入時に値がコピーされます。
構造体は、比較的小さく、単一の値やまとまりを表し、値として扱うことが自然なデータに適しています。
3-4. enum(列挙型)の使い方
enumは、関連する定数を名前付きでまとめる値型です。
public enum OrderStatus{Pending,Processing,Shipped,Completed,Cancelled}利用時は、定義された名前を指定します。
OrderStatus status = OrderStatus.Processing;if (status == OrderStatus.Processing){Console.WriteLine("注文を処理中です。");}
数値を直接使うより、値の意味が分かりやすくなります。
int status = 1; // 1が何を意味するのか分かりにくいenumの基になる型は、特に指定しなければintです。
3-5. 値型がメモリ上でどのように扱われるか
「値型は必ずStackに置かれる」と説明されることがありますが、これは正確ではありません。
値型の格納場所は、その値がどこで使用されるかによって変わります。ローカル変数としてスタック上に置かれる場合もあれば、クラスのフィールドとしてヒープ上のオブジェクト内部に含まれる場合もあります。
public class Sample{public int Number { get; set; }}Numberは値型ですが、Sampleインスタンスの一部として扱われます。
重要なのは、値型か参照型かという分類は、単純な「StackかHeapか」ではなく、代入や引数渡しの意味に関係するという点です。
3-6. 値型のコピーで起きる挙動
値型をコピーすると、コピー元とコピー先は独立した値になります。
DateTime date1 = new DateTime(2026, 1, 1);DateTime date2 = date1;date2 = date2.AddDays(1);
Console.WriteLine(date1); // 2026/01/01Console.WriteLine(date2); // 2026/01/02
ただし、構造体の内部に参照型のフィールドが含まれている場合、その参照先まで複製されるわけではありません。通常の代入では、各フィールドがそのままコピーされる浅いコピーになります。
3-7. 値型を使うべきケース
値型が適しているのは、主に次のようなケースです。
数値や真偽値を表す
小さく単純なデータを表す
値として比較することが自然である
コピー後に独立して扱いたい
不変なデータとして設計しやすい
座標、色、日付、金額の構成要素などは、値型として表現しやすいデータです。
大きな構造体はコピーのコストが増えるため、何でもstructにすればよいわけではありません。
4. 参照型(Reference Types)とは
4-1. 参照型の基本概念
参照型の変数は、オブジェクトそのものではなく、オブジェクトを参照するための情報を保持します。
Person person = new Person();new Person()によってインスタンスが生成され、変数personはそのインスタンスを参照します。
複数の変数が同じインスタンスを参照することもできます。
Person a = new Person();Person b = a;この場合、aとbは同じPersonインスタンスを参照します。
4-2. string・object・class・array・delegateの特徴
代表的な参照型は次のとおりです。
string
文字列を表す参照型です。ただし、不変型であり、作成後の文字列オブジェクト自体は変更されません。
string text = "Hello";text += " C#";この処理では、元の文字列を書き換えるのではなく、新しい文字列が作られます。
object
C#のすべての型の基礎となる型です。
object value = 100;value = "文字列";class
関連するデータと処理をまとめた参照型です。
array
同じ型の複数の要素を格納する参照型です。
int[] numbers = { 1, 2, 3 };delegate
メソッドへの参照を表す参照型です。
Action action = () => Console.WriteLine("実行");action();4-3. class(クラス)とインスタンスの関係
クラスはオブジェクトの設計図、インスタンスは設計図から生成された具体的なデータです。
public class User{public string Name { get; set; } = "";public void Greet(){Console.WriteLine($"こんにちは、{Name}さん");}
}
インスタンスはnewを使って生成できます。
User user = new User();user.Name = "山田";user.Greet();同じクラスから複数のインスタンスを作成できます。
User user1 = new User { Name = "山田" };User user2 = new User { Name = "佐藤" };user1とuser2は別々のオブジェクトです。
4-4. 参照型がメモリ上でどのように扱われるか
一般的なクラスのインスタンスは、マネージドヒープ上に確保されます。変数は、そのインスタンスを参照する情報を保持します。
使用されなくなったマネージドオブジェクトは、ガベージコレクターによって自動的に回収されます。
ただし、メモリ管理の詳細はランタイムの実装や最適化にも依存します。初心者の段階では、次の点を理解しておけば十分です。
参照型の代入ではオブジェクト全体が複製されるわけではない
複数の変数が同じオブジェクトを指せる
参照型の変数は
nullを表せる場合がある
4-5. 参照型の代入で起きる挙動
参照型を別の変数へ代入すると、通常は参照がコピーされます。
public class Counter{public int Value { get; set; }}Counter counter1 = new Counter { Value = 10 };Counter counter2 = counter1;
counter2.Value = 50;
Console.WriteLine(counter1.Value); // 50
別のインスタンスとして複製したい場合は、新しいオブジェクトを生成して値をコピーする必要があります。
Counter counter2 = new Counter{Value = counter1.Value};4-6. null参照とNullReferenceException
参照型の変数がどのオブジェクトも参照していない状態をnullと呼びます。
string? message = null;nullに対してプロパティやメソッドへアクセスすると、NullReferenceExceptionが発生する可能性があります。
Console.WriteLine(message.Length); // 例外の可能性安全にアクセスするには、nullチェックやnull条件演算子を使います。
if (message is not null){Console.WriteLine(message.Length);}int? length = message?.Length;4-7. 参照型を使うべきケース
参照型は、次のようなケースに適しています。
複数の場所から同じデータを共有したい
複雑な状態や振る舞いを持たせたい
継承やポリモーフィズムを利用したい
比較的大きなデータを扱いたい
オブジェクトの同一性が重要である
ユーザー、注文、商品、サービスなど、業務上の実体を表す場合はクラスがよく使われます。
5. 値型と参照型の違いをコード例で比較
5-1. 代入時の違い
値型では値がコピーされます。
int x = 10;int y = x;y = 30;
Console.WriteLine(x); // 10
参照型では参照がコピーされます。
List<int> list1 = new List<int> { 1, 2 };List<int> list2 = list1;list2.Add(3);
Console.WriteLine(list1.Count); // 3
5-2. メソッド引数に渡したときの違い
C#の引数は、特に指定しなければ値渡しです。
値型を渡すと、値のコピーがメソッドへ渡されます。
static void ChangeNumber(int number){number = 100;}int value = 10;ChangeNumber(value);
Console.WriteLine(value); // 10
参照型を値渡しすると、参照のコピーが渡されます。参照先のオブジェクトは変更できます。
static void AddItem(List<int> numbers){numbers.Add(100);}List<int> values = new List<int>();AddItem(values);
Console.WriteLine(values.Count); // 1
ただし、メソッド内で引数変数そのものに別のオブジェクトを代入しても、呼び出し元の変数は変わりません。
static void ReplaceList(List<int> numbers){numbers = new List<int> { 999 };}呼び出し元の変数自体を変更したい場合は、refやoutを使用します。
5-3. ==演算子で比較したときの違い
値型では、多くの場合、値の内容が比較されます。
int a = 10;int b = 10;Console.WriteLine(a == b); // True
一般的なクラスで==を使うと、演算子がオーバーロードされていない限り、同じインスタンスを参照しているかどうかが比較されます。
Person person1 = new Person { Name = "田中" };Person person2 = new Person { Name = "田中" };Console.WriteLine(person1 == person2); // 通常はFalse
stringは参照型ですが、==演算子が内容比較を行うように定義されています。
string text1 = "C#";string text2 = "C#";Console.WriteLine(text1 == text2); // True
recordも、既定で値ベースの等価比較を提供します。
5-4. StackとHeapの違い
Stackは、一般にメソッド呼び出しやローカル変数などの管理に使われます。Heapは、主に動的に生成されたオブジェクトの保存に使われます。
ただし、次のように単純化しすぎないことが重要です。
値型だから必ずStackにあるわけではない
参照型変数がどこに置かれるかは状況による
クラス内の値型フィールドはオブジェクトの一部になる
JITコンパイラの最適化によって実際の配置が変わることもある
C#のコードを書く際は、保存場所だけでなく、コピーと共有の意味を理解することが重要です。
5-5. 初心者が混同しやすいポイント
特に混同しやすいのは、「参照型」と「参照渡し」の違いです。
参照型は型の分類です。参照渡しは、ref、out、inなどを使った引数の渡し方です。
static void Replace(ref Person person){person = new Person { Name = "新しいユーザー" };}参照型を通常の引数として渡す場合は、「参照の値渡し」です。参照型だから自動的に参照渡しになるわけではありません。
5-6. 値型と参照型の使い分け早見表
| 比較項目 | 値型 | 参照型 |
|---|---|---|
| 代表例 | int、bool、struct、enum | class、string、配列 |
| 代入 | 値をコピー | 参照をコピー |
| 既定のnull | 通常は不可 | nullを表せる |
| Nullable | int?など | string?など |
| 同じデータの共有 | 基本的にしない | 可能 |
| 継承 | 構造体のクラス継承は不可 | クラス継承が可能 |
| 主な用途 | 小さな値、数値、状態 | 複雑なオブジェクト |
6. C#の型変換(Type Conversion)
6-1. 型変換とは
型変換とは、ある型の値を別の型として扱えるように変換することです。
int number = 100;long largeNumber = number;型変換には、主に次の方法があります。
暗黙的な型変換
明示的な型変換
ConvertクラスParseとTryParseas演算子パターンマッチング
6-2. 暗黙的な型変換
データが失われる危険性が低い変換は、自動的に行われる場合があります。
int number = 100;long largeNumber = number;double decimalNumber = number;intの範囲はlongの範囲に収まるため、明示的なキャストは不要です。
派生クラスから基底クラスへの変換も暗黙的に行えます。
Dog dog = new Dog();Animal animal = dog;6-3. 明示的な型変換(キャスト)
データが失われる可能性がある変換では、キャストが必要です。
double value = 10.8;int number = (int)value;Console.WriteLine(number); // 10
小数部分は切り捨てられます。
大きな整数型から小さな整数型へ変換すると、値が範囲外になる可能性があります。
long largeValue = 3_000_000_000;int smallValue = (int)largeValue;オーバーフローを検出したい場合は、checkedを利用できます。
int smallValue = checked((int)largeValue);6-4. Convertクラスを使った型変換
Convertクラスは、数値、文字列、真偽値などの変換に利用できます。
string text = "123";int number = Convert.ToInt32(text);int value = 1;bool result = Convert.ToBoolean(value);Convert.ToInt32は、小数値を整数へ変換する際に丸めを行います。単純なキャストによる切り捨てとは挙動が異なる場合があります。
double value = 10.8;int castResult = (int)value; // 10int convertResult = Convert.ToInt32(value); // 11
変換できない文字列を指定すると、FormatExceptionなどが発生します。
6-5. ParseとTryParseの違い
Parseは、文字列を対象の型へ変換します。変換できない場合は例外が発生します。
string input = "123";int number = int.Parse(input);int number = int.Parse("abc"); // FormatExceptionTryParseは、変換の成功可否をboolで返します。
string input = "123";if (int.TryParse(input, out int number)){Console.WriteLine($"変換成功: {number}");}else{Console.WriteLine("整数として解釈できません。");}
ユーザー入力や外部データなど、形式が保証されていない文字列にはTryParseが適しています。
6-6. as演算子とis演算子
as演算子は、互換性のある参照型やNullable値型への変換に使用できます。変換できない場合は例外ではなくnullになります。
object value = "C#";string? text = value as string;if (text is not null){Console.WriteLine(text);}
is演算子は、値が特定の型と互換性を持つか確認します。
if (value is string){Console.WriteLine("文字列です");}現在のC#では、型チェックと変数への代入を同時に行うパターンマッチングが便利です。
if (value is string message){Console.WriteLine(message.Length);}6-7. ボックス化とアンボックス化
値型をobject型や対応するインターフェイス型として扱うために、オブジェクトへ包む処理をボックス化と呼びます。
int number = 100;object boxed = number;ボックス化された値を元の値型へ戻す処理がアンボックス化です。
int unboxed = (int)boxed;アンボックス化では、ボックス化された実際の値型と一致する型を指定する必要があります。
object boxed = 100;// long value = (long)boxed; // InvalidCastExceptionint number = (int)boxed;long value = number;
大量のボックス化は、オブジェクトの割り当てや変換処理につながるため、ジェネリック型を使って回避できる場合があります。
6-8. 型変換で起きやすいエラーと対処法
型変換では、次のようなエラーが発生します。
FormatException
文字列の形式が不正な場合に発生します。
int.Parse("ABC");対策としてTryParseを使用します。
InvalidCastException
互換性のない参照型へキャストした場合や、誤った型でアンボックス化した場合に発生します。
object value = "C#";int number = (int)value;対策として、isによるパターンマッチングを利用します。
OverflowException
checkedコンテキストや一部の変換処理で、値が変換先の範囲を超えた場合に発生します。
型変換前に、入力形式、null、数値範囲を確認することが大切です。
7. Nullable型とnull許容参照型
7-1. Nullable型とは
通常の値型にはnullを代入できません。
int number = null; // コンパイルエラー値型でnullを表現したい場合は、Nullable型を使用します。
int? number = null;int?はNullable<int>の短縮記法です。
7-2. int?やbool?の使い方
int?は、整数値またはnullを保持できます。
int? age = null;age = 20;age = null;
bool?は、true、false、nullの3つの状態を表現できます。
bool? answer = null;「はい」「いいえ」に加えて「未回答」を表現したい場合に便利です。
7-3. Nullableの基本
Nullable値型の正式な型はNullable<T>です。
Nullable<int> number = null;次の記述と同じ意味です。
int? number = null;Tには値型を指定します。
DateTime? completedAt = null;decimal? discountRate = null;stringは参照型なので、Nullable<string>とは記述できません。参照型のnull許容性にはstring?などの注釈を使用します。
7-4. HasValueとValueの使い方
Nullable値型には、値が存在するか確認するHasValueプロパティがあります。
int? number = 10;if (number.HasValue){Console.WriteLine(number.Value);}
Valueは内部の値を取得します。ただし、値がnullのときにValueへアクセスすると、InvalidOperationExceptionが発生します。
通常は、パターンマッチングを使うと簡潔です。
if (number is int value){Console.WriteLine(value);}7-5. null合体演算子 ?? の使い方
??演算子は、左辺がnullの場合に代替値を使用します。
int? input = null;int result = input ?? 0;Console.WriteLine(result); // 0
参照型にも使用できます。
string? name = null;string displayName = name ?? "名前未設定";??=を使うと、変数がnullの場合だけ値を代入できます。
string? message = null;message ??= "既定のメッセージ";7-6. null条件演算子 ?. の使い方
?.演算子は、対象がnullでない場合だけメンバーへアクセスします。
string? text = null;int? length = text?.Length;対象がnullなら、例外を発生させずに結果がnullになります。
メソッド呼び出しにも使用できます。
customer?.SendNotification();配列やコレクションのインデックスアクセスには?[]を利用できます。
int? firstValue = numbers?[0];7-7. null許容参照型とは
null許容参照型は、参照型の変数がnullになる可能性をコンパイラに伝える仕組みです。
string name = "田中";string? middleName = null;null許容参照型が有効な環境では、stringは原則としてnullを想定しない参照、string?はnullになる可能性がある参照として扱われます。
string? message = GetMessage();Console.WriteLine(message.Length);このコードでは、nullの可能性について警告が表示されます。
if (message is not null){Console.WriteLine(message.Length);}null許容参照型は、新しいランタイム型を作る仕組みではなく、主にコンパイル時の静的解析機能です。
7-8. Nullable型とnull許容参照型の違い
int?とstring?は見た目が似ていますが、仕組みは異なります。
| 項目 | int? | string? |
| 元の分類 | 値型 | 参照型 |
| 仕組み | Nullable<int> | null許容注釈 |
| 実行時の型 | Nullable値型 | 基本的にstring |
| 主な目的 | 値型でnullを保持する | nullの可能性を静的解析する |
どちらもnull関連の不具合を防ぐために重要ですが、同じ機能ではありません。
8. var・dynamic・objectの違い
8-1. varとは
varは、右辺の式からコンパイラに型を推論させるためのキーワードです。
var number = 100;var message = "Hello";コンパイル時には、それぞれ次の型として扱われます。
int number = 100;string message = "Hello";varで宣言した変数も静的型付けです。
var number = 100;// number = "文字列"; // コンパイルエラー初期化式が必要であり、型を判断できない書き方はできません。
// var value; // コンパイルエラー// var value = null; // 型を推論できない8-2. dynamicとは
dynamicは、メンバーへのアクセスや演算の解決を主に実行時まで遅らせる型です。
dynamic value = 100;Console.WriteLine(value + 20);value = "C#";Console.WriteLine(value.Length);
dynamic変数には異なる種類の値を代入できます。
ただし、存在しないメンバーを呼び出してもコンパイル時に検出されない場合があります。
dynamic value = "C#";value.NotExists(); // 実行時エラー8-3. objectとは
objectは、C#のすべての型の基底となる型です。
object value1 = 100;object value2 = "C#";object value3 = new Person();ただし、object型の変数から実際の型固有の機能を使うには、型確認や変換が必要です。
object value = "Hello";if (value is string text){Console.WriteLine(text.Length);}
8-4. varとdynamicの違い
varとdynamicの最大の違いは、型チェックのタイミングです。
var text = "C#";// text.NotExists(); // コンパイルエラーdynamic text = "C#";text.NotExists(); // コンパイルは通るが実行時エラー| 項目 | var | dynamic |
| 型の決定 | コンパイル時 | 実行時の値に基づいて処理 |
| 静的型チェック | あり | 一部が実行時まで遅延 |
| 型の変更 | 不可 | 異なる型の値を代入可能 |
| 入力補完 | 利用しやすい | 制限される場合がある |
| 安全性 | 比較的高い | 実行時エラーに注意 |
8-5. dynamicを使う際の注意点
dynamicは便利ですが、型安全性が低下します。
スペルミスをコンパイル時に発見しにくい
リファクタリングの影響を追跡しにくい
実行時エラーが発生しやすい
コードの意図を理解しにくくなる場合がある
COM連携、動的な外部API、リフレクションに近い処理などでは役立つことがありますが、通常の業務ロジックで安易に使うべきではありません。
8-6. 初心者はどの型指定を使うべきか
初心者は、基本的に具体的な型またはvarを使用するとよいでしょう。
int count = 10;string name = "田中";var products = new List<Product>();
右辺から型が明確に分かる場合は、varを使うと重複を減らせます。
Dictionary<string, List<int>> data =new Dictionary<string, List<int>>();var data = new Dictionary<string, List<int>>();一方、型が分かりにくくなる場合は明示的に記述します。dynamicは、必要性を説明できる場合に限定するのが安全です。
9. C#でよく使う組み込み型一覧
9-1. 整数型:byte・short・int・long
主な整数型は次のとおりです。
| C#型 | .NET型 | ビット数 | おおよその用途 |
byte | System.Byte | 8 | バイナリデータ |
short | System.Int16 | 16 | 小さな整数 |
int | System.Int32 | 32 | 一般的な整数 |
long | System.Int64 | 64 | 大きな整数 |
通常の整数にはintが最もよく使われます。
int count = 1_000;long population = 8_000_000_000L;符号なし整数型には、byte、ushort、uint、ulongなどがあります。ただし、外部APIや演算との互換性も考えて選択する必要があります。
9-2. 小数型:float・double・decimal
C#には、主に3種類の小数型があります。
float floatValue = 1.5f;double doubleValue = 1.5;decimal decimalValue = 1.5m;float
32ビットの浮動小数点型です。メモリ使用量を抑えたいグラフィックス処理などで使われます。
double
64ビットの浮動小数点型です。科学技術計算や一般的な小数計算でよく使われます。
decimal
128ビットの10進数型です。金額など、10進数としての精度が重要な処理に適しています。
浮動小数点型では、10進小数を厳密に表現できない場合があります。
double result = 0.1 + 0.2;Console.WriteLine(result == 0.3); // Falseになる場合がある9-3. 真偽値型:bool
boolは、trueまたはfalseを表す値型です。
bool isLoggedIn = true;bool hasPermission = false;条件式で使用します。
if (isLoggedIn){Console.WriteLine("ログイン済みです");}C#では、整数を暗黙的に真偽値として扱うことはできません。
int value = 1;// if (value) { } // コンパイルエラー9-4. 文字型:char
charは、1つのUTF-16コード単位を表す値型です。
char letter = 'A';char japanese = 'あ';文字リテラルはシングルクォートで囲みます。
char newline = '\n';すべての見た目上の文字が1つのcharに収まるとは限りません。一部のUnicode文字は複数のcharで表現されます。
9-5. 文字列型:string
stringは文字列を表す参照型です。
string name = "田中";文字列補間を使うと、値を埋め込めます。
int age = 25;string message = $"{name}さんは{age}歳です。";逐語的文字列リテラルでは、バックスラッシュをそのまま記述しやすくなります。
string path = @"C:\Users\Sample";文字列は不変です。大量の文字列連結を繰り返す場合は、StringBuilderの利用を検討します。
9-6. object型
objectには、あらゆる型の値を代入できます。
object number = 10;object text = "Hello";object flag = true;値型をobjectへ代入するとボックス化が行われます。
柔軟性は高いものの、実際の値を利用する際に型確認や変換が必要です。型が事前に分かっているなら、具体的な型やジェネリック型を使う方が安全です。
9-7. 日付や時間を扱うDateTime
DateTimeは、日付と時刻を扱う構造体、つまり値型です。
DateTime now = DateTime.Now;DateTime today = DateTime.Today;DateTime specified = new DateTime(2026, 6, 19);文字列から変換する場合は、DateTime.TryParseを利用できます。
if (DateTime.TryParse("2026-06-19", out DateTime date)){Console.WriteLine(date);}期間はTimeSpanで表します。
TimeSpan duration = TimeSpan.FromHours(2);DateTime later = now.Add(duration);タイムゾーンを含む時点を扱う場合は、DateTimeOffsetが適しているケースもあります。
10. ユーザー定義型の基本
10-1. classの基本
classは、状態と振る舞いをまとめる参照型です。
public class BankAccount{public string OwnerName { get; }public decimal Balance { get; private set; }public BankAccount(string ownerName){OwnerName = ownerName;}public void Deposit(decimal amount){if (amount <= 0){throw new ArgumentOutOfRangeException(nameof(amount));}Balance += amount;}
}
クラスは、継承、カプセル化、ポリモーフィズムなどのオブジェクト指向機能を利用できます。
10-2. structの基本
structはユーザー定義の値型です。
public readonly struct Temperature{public double Celsius { get; }public Temperature(double celsius){Celsius = celsius;}
}
readonly structにすると、構造体全体を不変として設計しやすくなります。
構造体は値としてコピーされるため、サイズが大きい型や頻繁に変更する型には注意が必要です。
10-3. enumの基本
enumは、限定された選択肢を型として表します。
public enum PaymentMethod{CreditCard,BankTransfer,Cash}メソッドの引数に使用すると、受け付ける値の意味を明確にできます。
void Pay(PaymentMethod method){// 支払い処理}複数の値を組み合わせる用途では、[Flags]属性を使用する設計もあります。
[Flags]public enum Permission{None = 0,Read = 1,Write = 2,Delete = 4}10-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("処理を開始しました");10-5. recordの基本
recordは、データを中心とした型を簡潔に定義できる仕組みです。
public record User(string Name, int Age);User user1 = new User("田中", 25);User user2 = new User("田中", 25);Console.WriteLine(user1 == user2); // True
通常のクラスでは既定で参照の同一性が重視されますが、レコードでは値ベースの等価比較が提供されます。
with式を使うと、一部の値を変更したコピーを作成できます。
User olderUser = user1 with { Age = 26 };record classは参照型、record structは値型です。
10-6. class・struct・recordの違い
| 項目 | class | struct | record class |
| 分類 | 参照型 | 値型 | 参照型 |
| 代入 | 参照をコピー | 値をコピー | 参照をコピー |
| 既定の等価性 | 主に参照 | 値ベース | 値ベース |
| 継承 | 対応 | クラス継承不可 | 対応 |
| 主な用途 | 状態と振る舞い | 小さな値 | データモデル |
| null | 許容可能 | 通常は不可 | 許容可能 |
クラスか構造体かは、単に処理速度で選ぶのではなく、データを「同一性のあるオブジェクト」として扱うか、「独立した値」として扱うかで判断します。
11. ジェネリック型の基本
11-1. ジェネリック型とは
ジェネリック型は、扱う型をパラメーターとして指定できる仕組みです。
List<int> numbers = new List<int>();List<string> names = new List<string>();List<T>のTが型パラメーターです。使用時にintやstringなどの具体的な型を指定します。
独自のジェネリック型も定義できます。
public class Box<T>{public T Value { get; set; }public Box(T value){Value = value;}
}
Box<int> numberBox = new Box<int>(100);Box<string> textBox = new Box<string>("C#");11-2. Listの使い方
List<T>は、要素数を動的に増減できるコレクションです。
List<string> names = new List<string>();names.Add("田中");names.Add("佐藤");names.Remove("田中");
コレクション初期化子も使用できます。
List<int> numbers = new List<int>{10,20,30};指定した型以外の値は追加できません。
List<int> numbers = new List<int>();// numbers.Add("100"); // コンパイルエラー11-3. Dictionary<TKey, TValue>の使い方
Dictionary<TKey, TValue>は、キーと値の組み合わせを管理します。
Dictionary<string, int> scores = new Dictionary<string, int>{["田中"] = 80,["佐藤"] = 90};値を取得する際は、キーが存在しない可能性を考慮します。
if (scores.TryGetValue("田中", out int score)){Console.WriteLine(score);}TKeyがキーの型、TValueが値の型です。
11-4. ジェネリックメソッドの基本
メソッド単位で型パラメーターを定義できます。
static T GetFirst<T>(T[] values){if (values.Length == 0){throw new ArgumentException("配列が空です。", nameof(values));}return values<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">[0]</span>;
}
int firstNumber = GetFirst(new[] { 10, 20, 30 });string firstName = GetFirst(new[] { "田中", "佐藤" });型制約を付けると、指定できる型を限定できます。
static void PrintName<T>(T item) where T : Person{Console.WriteLine(item.Name);}11-5. 型安全性とジェネリックのメリット
ジェネリックには、次のメリットがあります。
コンパイル時に型をチェックできる
キャストを減らせる
再利用可能なコードを書ける
値型の不要なボックス化を避けられる
IDEの入力補完を利用しやすい
ジェネリックを使わずobjectだけで実装すると、取り出す際にキャストが必要です。
object value = 100;int number = (int)value;ジェネリック型なら、最初から型が保証されます。
List<int> values = new List<int> { 100 };int number = values[0];12. C# Typesで初心者がつまずきやすいポイント
12-1. stringは値型ではなく参照型
stringは値のように扱える場面が多いため、値型だと誤解されやすい型です。しかし、stringは参照型です。
string a = "Hello";string b = a;b = "World";
Console.WriteLine(a); // Hello
bの変更がaに影響しないのは、stringが不変型だからです。文字列の変更時には、新しい文字列オブジェクトが作られます。
また、stringの==演算子は、通常、参照ではなく文字列の内容を比較します。
12-2. nullを代入できる型とできない型
通常の値型にはnullを代入できません。
// int number = null;Nullable値型にすれば代入できます。
int? number = null;参照型は実行時にnullを保持できます。null許容参照型が有効な場合は、string?のようにnullの可能性を明示します。
string? name = null;12-3. intとint?の違い
intは必ず整数値を持ちます。フィールドなどの既定値は0です。
int?は整数値に加えてnullを持てます。
int count = 0;int? optionalCount = null;0とnullの意味は異なります。
0:値が存在し、その値がゼロnull:値が存在しない、または未設定
データベースのNULLや未入力項目を扱うときに重要な違いです。
12-4. objectに何でも入れられる理由
すべてのC#の型は、最終的にobject型の体系に含まれます。そのため、参照型のインスタンスだけでなく、値型の値もobjectへ代入できます。
object a = 10;object b = "C#";object c = new Person();値型の場合はボックス化が行われます。
何でも格納できる反面、取り出すときの型安全性は低くなります。型が分かっている場合は、具体的な型やジェネリック型を優先します。
12-5. キャスト失敗による例外
次のコードはコンパイルできますが、実行時にInvalidCastExceptionが発生します。
object value = "123";int number = (int)value;文字列の"123"は、内部的にintとして格納されているわけではありません。数値へ変換したい場合は、int.Parseやint.TryParseを使います。
if (int.TryParse((string)value, out int number)){Console.WriteLine(number);}型の互換性を確認する場合は、isを使用します。
12-6. varは動的型ではない
varは、コンパイラによる型推論です。
var value = 100;これは実質的に次と同じです。
int value = 100;型はコンパイル時に決まっているため、後から文字列を代入できません。
// value = "100"; // コンパイルエラー実行時にメンバー解決を行うdynamicとは異なります。
12-7. 値渡しと参照渡しの混同
C#では、値型か参照型かに関係なく、通常の引数は値渡しです。
参照型を引数にすると、オブジェクトへの参照が値としてコピーされます。
static void Update(Person person){person.Name = "変更後";}これは参照先のオブジェクトを変更しています。一方、次の代入は呼び出し元の変数を置き換えません。
static void Replace(Person person){person = new Person();}変数自体を参照渡ししたい場合は、refを使用します。
static void Replace(ref Person person){person = new Person();}13. C# Typesの実践的な使い分け
13-1. 数値を扱うときの型選び
通常の整数には、まずintを検討します。
int quantity = 10;intの範囲を超える可能性がある場合はlongを使います。
long fileSize = 5_000_000_000L;小さな範囲しか使わないからといって、常にbyteやshortを選ぶ必要はありません。演算やAPIとの互換性、可読性も考慮します。
金額には、通常decimalが適しています。
decimal price = 1980.50m;13-2. 文字列を扱うときの型選び
文章、名前、識別子などの文字列にはstringを使用します。
string customerName = "田中";1つのUTF-16コード単位を扱う場合はcharを使います。
char separator = ',';大量の文字列をループで連結する場合は、StringBuilderを検討します。
var builder = new System.Text.StringBuilder();for (int i = 0; i < 1000; i++){builder.Append(i);}
string result = builder.ToString();
13-3. nullを許容する設計の考え方
nullを許容するかどうかは、データの意味から判断します。
必須の名前なら、nullを許容しないstringとして設計します。
public string Name { get; set; } = "";任意の備考なら、string?が適しています。
public string? Note { get; set; }未入力の日付を表すなら、DateTime?を利用できます。
public DateTime? CompletedAt { get; set; }「念のためすべてNullableにする」のではなく、nullが業務上どのような意味を持つかを明確にすることが重要です。
13-4. classとstructの使い分け
次のようなデータには、クラスが適しています。
複数の場所で同じインスタンスを共有する
状態が時間とともに変化する
継承を利用する
オブジェクトの同一性が重要である
public class Customer{public int Id { get; set; }public string Name { get; set; } = "";}次のようなデータには、構造体が適しています。
小さく単純である
値として比較する
不変に設計できる
コピーされても自然である
public readonly struct Coordinate{public double X { get; }public double Y { get; }public Coordinate(double x, double y){X = x;Y = y;}
}
迷う場合はクラスから検討し、値として扱う明確な理由がある場合に構造体を選ぶとよいでしょう。
13-5. decimal・double・floatの使い分け
decimalが向いている用途
金額
税率計算
10進数としての精度が重要な業務計算
decimal total = 1980.50m;doubleが向いている用途
科学技術計算
統計処理
座標計算
一般的な浮動小数点演算
double distance = 123.456;floatが向いている用途
3Dグラフィックス
ゲーム開発
メモリ使用量が重要な大量データ
floatを要求するAPI
float opacity = 0.5f;金額計算にdoubleを使うと、丸め誤差が問題になる場合があります。
13-6. 型変換を安全に行う実装パターン
外部から受け取った文字列を変換する場合は、TryParseを優先します。
static bool TryReadAge(string input, out int age){if (!int.TryParse(input, out age)){return false;}return age >= 0 && age <= 150;
}
参照型を変換する場合は、パターンマッチングを使います。
static void PrintLength(object value){if (value is string text){Console.WriteLine(text.Length);}}数値の縮小変換では、必要に応じてcheckedを使います。
static int ToInt32(long value){return checked((int)value);}安全な型変換では、次の3点を確認します。
入力値がnullではないか
入力形式が正しいか
変換先の範囲に収まるか
14. C# Typesに関するよくある質問
14-1. C#の型にはどんな種類がありますか?
C#の型は、大きく値型と参照型に分けられます。
値型には、int、double、bool、char、struct、enumなどがあります。参照型には、class、string、配列、delegate、interfaceなどがあります。
そのほか、Nullable型、ジェネリック型、タプル型、ポインター型などがあります。
14-2. 値型と参照型の違いは何ですか?
値型の変数には値が格納され、代入時には通常その値がコピーされます。
参照型の変数にはオブジェクトへの参照が格納され、代入時には通常その参照がコピーされます。複数の変数が同じオブジェクトを参照できるため、一方から加えた変更が他方にも見えることがあります。
14-3. stringは値型ですか?参照型ですか?
stringは参照型です。
ただし、文字列は不変であり、==演算子が内容比較を行うように定義されているため、値型に似た感覚で使用できる場面があります。
14-4. int?とは何ですか?
int?は、整数またはnullを保持できるNullable値型です。
int? value = null;正式にはNullable<int>であり、未入力、未設定、不明といった状態を整数とは別に表現できます。
14-5. varとdynamicの違いは何ですか?
varはコンパイル時に型が決まる静的型付けの仕組みです。
var value = 10; // intとして決定dynamicは、メンバーの解決などを実行時まで遅らせます。
dynamic value = 10;value = "C#";dynamicではコンパイル時に検出できない間違いが実行時エラーになる可能性があります。
14-6. キャストと型変換の違いは何ですか?
型変換は、ある型から別の型へ変換する処理全般を指します。
キャストは、その型変換方法の一つです。
double value = 10.5;int number = (int)value;文字列を整数へ変換するint.ParseやConvert.ToInt32も型変換ですが、一般的にはキャストとは区別して説明されます。
14-7. Nullableはいつ使うべきですか?
値が存在しない状態を、通常の値と区別する必要がある場合に使用します。
たとえば、次のようなデータです。
未入力の年齢
完了していない処理の完了日時
回答されていない真偽値
データベースのNULL列
任意入力の値
int? age = null;DateTime? completedAt = null;bool? answer = null;一方、必ず値が存在すべきデータまでNullableにすると、不要なnullチェックが増えます。業務上、nullが何を意味するのかを明確にしたうえで使用します。
まとめ
C# Typesを理解するうえで、最初に押さえるべきポイントは値型と参照型の違いです。
値型は、intやbool、structなどのように値として扱われ、代入すると通常は値がコピーされます。参照型は、classやstring、配列などのようにオブジェクトへの参照を保持し、代入すると通常は参照がコピーされます。
型変換では、暗黙的変換、キャスト、Convert、Parse、TryParseを状況に応じて使い分けます。外部入力の変換では、例外を避けやすいTryParseが有効です。
値型でnullを表現する場合はint?などのNullable値型を使い、参照型のnull可能性はstring?などのnull許容参照型で明示します。
また、varはコンパイル時に型が決まる型推論であり、dynamicとは異なります。型が分かっている場面では、具体的な型やジェネリック型を活用することで、安全性と可読性を高められます。
C#の型は、変数宣言のためだけの仕組みではありません。データの意味、コピー方法、nullの扱い、変換の安全性、オブジェクト設計まで左右する、C#プログラミングの土台です。

