C#の型とは?値型・参照型・基本データ型の違いを初心者向けに徹底解説

はじめに

C#を学び始めると、最初に「int」「string」「bool」といったさまざまな型が登場します。しかし、初心者のうちは「なぜ型を指定する必要があるのか」「値型と参照型は何が違うのか」と疑問に感じることも多いでしょう。

C#の型は、変数に保存できるデータの種類や、そのデータに対して実行できる操作を決める重要な仕組みです。型を正しく理解すると、プログラムのエラーを減らし、メモリの使われ方やデータの受け渡しも理解しやすくなります。

この記事では、C#の型について、値型・参照型・基本データ型・型変換・nullable型・boxingなどを、初心者向けにコード例を交えながら解説します。

1. C#の型とは?初心者向けに意味をわかりやすく解説

1-1. 型とはデータの種類を決める仕組み

C#における型とは、変数がどのようなデータを扱うのかを決める仕組みです。

例えば、整数を保存する場合はint、文字列を保存する場合はstring、真偽値を保存する場合はboolを使用します。

int age = 20;string name = "田中";bool isMember = true;

このコードでは、それぞれの変数が次のようなデータを扱っています。

変数保存しているデータ
ageint整数
namestring文字列
isMemberbooltrueまたはfalse

型によって、変数に代入できる値や実行できる処理が変わります。

例えば、int型の変数には整数を代入できますが、文字列をそのまま代入することはできません。

int age = 20;

// コンパイルエラー// age = "二十";

1-2. C#で型が必要な理由

C#で型が必要な主な理由は、プログラムを安全かつ効率的に実行するためです。

型が決まっていると、コンパイラはプログラムの実行前に不正な処理を発見できます。

int number = 10;

// int型とstring型はそのまま加算できない// int result = number + "20";

このような間違いは、プログラムを実行する前のコンパイル時に検出されます。

また、型が分かれば、コンピューターはデータを保存するために必要な領域や、そのデータに対して実行できる処理を判断できます。

例えば、整数同士であれば加算や減算ができ、文字列であれば連結や検索ができます。

int total = 10 + 20;string fullName = "山田" + "太郎";

1-3. 型を理解すると何ができるようになるのか

C#の型を理解すると、次のようなことができるようになります。

  • データに適した変数を選べる

  • 型の違いによるエラーを防げる

  • 値型と参照型の代入動作を理解できる

  • nullによるエラーを減らせる

  • 型変換を正しく行える

  • クラスや構造体を適切に設計できる

  • ジェネリック型を活用できる

特に重要なのが、値型と参照型の違いです。同じ代入処理でも、型の種類によってその後の動作が変わります。

2. C#の型の基本分類

2-1. 値型とは

値型とは、変数がデータそのものを保持する型です。

代表的な値型には、次のものがあります。

  • int

  • long

  • double

  • decimal

  • bool

  • char

  • struct

  • enum

値型の変数を別の変数に代入すると、基本的には値がコピーされます。

int a = 10;int b = a;

b = 30;

Console.WriteLine(a); // 10Console.WriteLine(b); // 30

aの値をbに代入した後、bを変更してもaには影響しません。

2-2. 参照型とは

参照型とは、オブジェクトそのものではなく、オブジェクトを参照するための情報を変数が保持する型です。

代表的な参照型には、次のものがあります。

  • class

  • string

  • 配列

  • interface

  • delegate

  • object

参照型の変数を別の変数に代入すると、通常は同じオブジェクトを参照することになります。

class Person{public string Name { get; set; } = "";}

Person person1 = new Person();person1.Name = "田中";

Person person2 = person1;person2.Name = "佐藤";

Console.WriteLine(person1.Name); // 佐藤

person1person2は同じPersonオブジェクトを参照しているため、person2経由で名前を変更すると、person1から見える名前も変わります。

2-3. 値型と参照型の違いを比較表で解説

比較項目値型参照型
変数が保持するもの値そのものオブジェクトへの参照
代入時の動作値をコピーする参照をコピーする
代表例intboolstructenumclassstring、配列
標準状態でのnull基本的に代入できない代入できる場合がある
継承構造体は別のクラスや構造体を継承できないクラスは他のクラスを継承できる
変更の影響コピー元とコピー先は独立同じオブジェクトなら影響を共有する

ただし、値型が必ずスタック、参照型が必ずヒープに保存されると単純に考えるのは正確ではありません。実際の保存場所は、変数がローカル変数なのか、クラスのフィールドなのか、実行環境がどのように最適化するかなどによって変わります。

初心者のうちは、保存場所よりも「値型は値をコピーし、参照型は参照をコピーする」という違いを意識すると理解しやすいでしょう。

2-4. 型は大きく「値型」「参照型」「ポインター型」に分かれる

C#の型は、大きく次の3種類に分類できます。

  1. 値型

  2. 参照型

  3. ポインター型

ポインター型は、メモリアドレスを直接扱うための型です。

unsafe{int number = 10;int* pointer = &number;

Console.WriteLine(*pointer);

}

ポインターを使用するには、通常unsafeコンテキストと、プロジェクト側でunsafeコードを許可する設定が必要です。

一般的なC#アプリケーションでは、ポインター型を使う場面は多くありません。初心者は、まず値型と参照型を理解することが重要です。

3. C#の基本データ型一覧

3-1. 整数型:int・long・short・byte

整数を扱う主な型は、intlongshortbyteです。

サイズおおよその範囲
byte8ビット0~255
short16ビット-32,768~32,767
int32ビット約-21億~21億
long64ビット非常に大きな整数

一般的な整数には、まずintを使用します。

int age = 25;int score = 100;

intの範囲を超える可能性がある数値には、longを使用します。

long population = 8_000_000_000L;

数値の末尾に付けたLは、そのリテラルがlong型であることを表します。

shortbyteは、扱う値の範囲が明確に小さい場合や、バイナリデータを扱う場合などに使用されます。

byte red = 255;short temperature = -10;

3-2. 浮動小数点型:float・double・decimal

小数を扱う主な型は、floatdoubledecimalです。

特徴主な用途
float単精度浮動小数点数3D座標、画像処理など
double倍精度浮動小数点数一般的な小数計算
decimal10進数を高精度で扱う金額、会計計算

一般的な小数にはdoubleがよく使われます。

double height = 172.5;

float型のリテラルには、末尾にFまたはfを付けます。

float rate = 0.5F;

decimal型のリテラルには、末尾にMまたはmを付けます。

decimal price = 1980.50M;

floatdoubleは2進数を使って小数を表現するため、10進数の値を厳密に表現できない場合があります。

double result = 0.1 + 0.2;Console.WriteLine(result);

金額のように10進数としての正確さが重要な処理には、通常decimalが適しています。

3-3. 文字・文字列型:char・string

1文字を扱う場合はchar、複数の文字をまとめた文字列を扱う場合はstringを使用します。

char grade = 'A';string message = "こんにちは";

charの値はシングルクォーテーション、stringの値はダブルクォーテーションで囲みます。

char symbol = '#';string name = "鈴木";

stringは参照型ですが、内容を直接変更できないイミュータブルな型です。

string text = "Hello";text = text + " World";

この処理では、元の文字列自体が書き換えられるのではなく、連結後の新しい文字列が作成され、textがその文字列を参照するようになります。

3-4. 真偽値型:bool

bool型は、真偽値を表す型です。代入できる値はtrueまたはfalseです。

bool isActive = true;bool hasError = false;

boolは条件分岐や繰り返し処理でよく使用されます。

bool isAdult = true;

if (isAdult){Console.WriteLine("成人です");}

C#では、整数の01boolとして直接扱うことはできません。

// コンパイルエラー// bool value = 1;

3-5. object型とは

object型は、C#のすべての型の基礎となる型です。値型も参照型も、最終的にはobjectとして扱えます。

object value1 = 100;object value2 = "こんにちは";object value3 = true;

さまざまな型の値を代入できるため便利ですが、取り出した値を元の型として使うには、型の確認や変換が必要になることがあります。

object value = "C#";

if (value is string text){Console.WriteLine(text.Length);}

値型をobject型に代入すると、boxingが発生します。boxingについては後ほど詳しく解説します。

3-6. var型推論とは

varを使うと、右辺の値からコンパイラが変数の型を推論します。

var age = 20;var name = "田中";var isMember = true;

この場合、コンパイラは次のように型を決定します。

  • ageint

  • namestring

  • isMemberbool

varを使っても、変数の型がなくなるわけではありません。型はコンパイル時に確定し、その後に別の型の値を代入することはできません。

var number = 10;

// numberはint型なので文字列は代入できない// number = "10";

また、右辺から型を判断できない宣言はできません。

// コンパイルエラー// var value;

// 型を推論できない// var data = null;

4. 値型とは?特徴と使い方

4-1. 値型はデータそのものを保持する

値型の変数は、その値を表すデータを保持します。

int count = 5;bool isCompleted = false;

値型を別の変数に代入すると、それぞれの変数が独立した値を持つことになります。

int first = 10;int second = first;

second = 20;

Console.WriteLine(first); // 10Console.WriteLine(second); // 20

4-2. 値型の代表例

C#の代表的な値型には、次のものがあります。

  • 整数型:byteshortintlong

  • 符号なし整数型:ushortuintulong

  • 小数型:floatdoubledecimal

  • 文字型:char

  • 真偽値型:bool

  • 構造体:struct

  • 列挙型:enum

  • nullable値型:int?など

DateTimeもクラスのように見えますが、実際には構造体として定義された値型です。

DateTime today = DateTime.Today;

4-3. 値型の代入で起きること

値型の代入では、変数が持つ値がコピーされます。

int original = 100;int copy = original;

copy = 200;

Console.WriteLine(original); // 100Console.WriteLine(copy); // 200

構造体でも基本的な考え方は同じです。

struct Point{public int X;public int Y;}

Point point1 = new Point { X = 10, Y = 20 };Point point2 = point1;

point2.X = 99;

Console.WriteLine(point1.X); // 10Console.WriteLine(point2.X); // 99

point2を変更しても、コピー元であるpoint1には影響しません。

ただし、構造体のフィールドに参照型が含まれている場合、その参照自体がコピーされます。構造体のコピーによって、内部にある参照先のオブジェクトまで複製されるわけではありません。

4-4. structとenumも値型

structは、複数のデータをまとめて独自の値型を定義するために使用します。

struct Size{public int Width;public int Height;}

使用例は次のとおりです。

Size size = new Size{Width = 800,Height = 600};

Console.WriteLine(size.Width);

enumは、関連する定数をまとめて表現する値型です。

enum OrderStatus{Pending,Processing,Shipped,Completed}
OrderStatus status = OrderStatus.Processing;

if (status == OrderStatus.Processing){Console.WriteLine("処理中です");}

数値を直接使うよりも、enumを使ったほうが値の意味を理解しやすくなります。

4-5. 値型を使うときの注意点

値型は代入やメソッド呼び出しで値がコピーされます。そのため、サイズの大きな構造体を何度もコピーすると、処理コストが増える可能性があります。

また、値型の変数には、通常nullを代入できません。

int number = 10;

// コンパイルエラー// number = null;

nullを許可したい場合は、nullable値型を使います。

int? number = null;

独自のデータ型を作るときは、単純に「値型のほうが速そうだから」という理由だけでstructを選ばないことも大切です。

小さく、値として扱うのが自然で、不変に設計しやすいデータにはstructが適しています。一方、同一性を持つオブジェクトや、継承を利用したいデータにはclassが適しています。

5. 参照型とは?特徴と使い方

5-1. 参照型はデータの場所を保持する

参照型の変数は、オブジェクトを参照するための情報を保持します。

class Product{public string Name { get; set; } = "";}
Product product = new Product();product.Name = "キーボード";

new Product()によってProductオブジェクトが作成され、productはそのオブジェクトを参照します。

5-2. 参照型の代表例

C#の代表的な参照型には、次のものがあります。

  • クラス

  • 文字列

  • 配列

  • インターフェース

  • デリゲート

  • object

  • レコードクラス

例えば、配列は参照型です。

int[] numbers = { 1, 2, 3 };

stringも参照型ですが、内容を変更できないという特徴があります。

string text = "C#";

5-3. 参照型の代入で起きること

参照型の代入では、通常、参照がコピーされます。

class Counter{public int Value { get; set; }}

Counter counter1 = new Counter();counter1.Value = 10;

Counter counter2 = counter1;counter2.Value = 50;

Console.WriteLine(counter1.Value); // 50Console.WriteLine(counter2.Value); // 50

counter1counter2は同じオブジェクトを参照しています。そのため、一方からオブジェクトの内容を変更すると、もう一方から見える内容も変わります。

一方、新しいオブジェクトを代入すれば、別々のオブジェクトになります。

Counter counter1 = new Counter { Value = 10 };Counter counter2 = new Counter { Value = 10 };

counter2.Value = 50;

Console.WriteLine(counter1.Value); // 10Console.WriteLine(counter2.Value); // 50

5-4. class・interface・delegate・arrayは参照型

classは、データと処理をまとめたオブジェクトを定義するために使用します。

class User{public string Name { get; set; } = "";

public void Greet(){Console.WriteLine($"こんにちは、{Name}さん");}

}

interfaceは、クラスや構造体が実装すべき機能の契約を定義します。

interface IPrintable{void Print();}

delegateは、メソッドを参照するための型です。

delegate void MessageHandler(string message);

配列も参照型です。

int[] array1 = { 1, 2, 3 };int[] array2 = array1;

array2[0] = 99;

Console.WriteLine(array1[0]); // 99

5-5. null参照に注意する

参照型の変数は、オブジェクトを参照していない状態を表すnullになることがあります。

string? name = null;

nullの変数に対して、プロパティやメソッドへアクセスすると、実行時にNullReferenceExceptionが発生する可能性があります。

string? name = null;

// 実行時エラーになる可能性がある// Console.WriteLine(name.Length);

安全に扱うには、事前にnullを確認します。

if (name != null){Console.WriteLine(name.Length);}

null条件演算子?.を使う方法もあります。

Console.WriteLine(name?.Length);

6. 値型と参照型の違いをコード例で理解する

6-1. intを使った値型の代入例

intは値型です。

int a = 10;int b = a;

b = 20;

Console.WriteLine(a); // 10Console.WriteLine(b); // 20

b = aでは、aが持つ値の10bにコピーされます。そのため、後からbを変更してもaには影響しません。

6-2. classを使った参照型の代入例

クラスは参照型です。

class Box{public int Value { get; set; }}

Box box1 = new Box { Value = 10 };Box box2 = box1;

box2.Value = 20;

Console.WriteLine(box1.Value); // 20Console.WriteLine(box2.Value); // 20

box2 = box1では、オブジェクト自体ではなく、同じオブジェクトを指す参照がコピーされます。

したがって、box2.Valueを変更すると、box1から見える値も変わります。

6-3. メソッドに渡したときの違い

C#の引数は、通常は値渡しです。

値型をメソッドに渡すと、その値がコピーされます。

static void ChangeNumber(int number){number = 100;}

int value = 10;ChangeNumber(value);

Console.WriteLine(value); // 10

メソッド内のnumberを変更しても、呼び出し元のvalueには影響しません。

参照型を値渡しすると、オブジェクトへの参照がコピーされます。

class Person{public string Name { get; set; } = "";}

static void ChangeName(Person person){person.Name = "佐藤";}

Person user = new Person { Name = "田中" };ChangeName(user);

Console.WriteLine(user.Name); // 佐藤

メソッド内のpersonと呼び出し元のuserは、同じオブジェクトを参照しています。そのため、オブジェクトの内容を変更すると呼び出し元にも影響します。

ただし、メソッド内で引数そのものに別のオブジェクトを代入しても、呼び出し元の変数は変わりません。

static void ReplacePerson(Person person){person = new Person { Name = "鈴木" };}

Person user = new Person { Name = "田中" };ReplacePerson(user);

Console.WriteLine(user.Name); // 田中

呼び出し元の変数自体を変更する場合は、refなどを使用する必要があります。

static void ReplacePerson(ref Person person){person = new Person { Name = "鈴木" };}

6-4. 初心者が混乱しやすいポイント

初心者が混乱しやすいのは、「参照型は参照渡しである」という説明です。

正確には、通常の引数として参照型を渡した場合も、引数の渡し方は値渡しです。ただし、コピーされる値がオブジェクトへの参照であるため、呼び出し元とメソッド内で同じオブジェクトを操作できます。

整理すると、次のようになります。

  • 値型を値渡しする:値がコピーされる

  • 参照型を値渡しする:参照がコピーされる

  • refで渡す:変数そのものへの参照が渡される

また、stringは参照型ですが、内容を直接変更できません。

string first = "Hello";string second = first;

second = "World";

Console.WriteLine(first); // HelloConsole.WriteLine(second); // World

これは、secondが新しい文字列を参照するようになったためです。元の文字列オブジェクトが書き換えられたわけではありません。

7. C#の型変換の基本

7-1. 暗黙的な型変換

暗黙的な型変換とは、データを失う危険性が低い場合に、C#が自動的に行う型変換です。

int number = 100;long largeNumber = number;

intの値はlongの範囲に収まるため、自動で変換できます。

float value = 10.5F;double result = value;

このように、より広い範囲や精度を持つ型への変換は、暗黙的に実行できる場合があります。

ただし、すべての数値型で自由に変換できるわけではありません。例えば、doubleからdecimalへの変換は明示的に行う必要があります。

7-2. 明示的な型変換

明示的な型変換とは、プログラマーが変換先の型を指定する方法です。キャストとも呼ばれます。

double value = 10.8;int number = (int)value;

Console.WriteLine(number); // 10

doubleからintへ変換すると、小数部分は四捨五入されず切り捨てられます。

また、変換先の型に収まらない値をキャストすると、意図しない結果になる可能性があります。

int number = 300;byte value = (byte)number;

Console.WriteLine(value);

オーバーフローを例外として検出したい場合は、checkedを利用できます。

checked{int number = 300;byte value = (byte)number;}

7-3. Parse・TryParse・Convertの違い

文字列を数値に変換するときは、ParseTryParseConvertなどを使用できます。

Parseは、変換に失敗すると例外が発生します。

string text = "123";int number = int.Parse(text);

Console.WriteLine(number);

変換できない文字列の場合、FormatExceptionなどが発生します。

// 例外が発生する// int number = int.Parse("abc");

TryParseは、変換に成功したかどうかをboolで返します。

string text = "123";

if (int.TryParse(text, out int number)){Console.WriteLine($"変換成功: {number}");}else{Console.WriteLine("変換できませんでした");}

ユーザー入力のように、正しい数値が入力されるとは限らない場面では、TryParseが適しています。

Convertは、さまざまな型同士の変換に使用できます。

string text = "123";int number = Convert.ToInt32(text);

nullに対する動作にも違いがあります。

string? text = null;

int number = Convert.ToInt32(text);Console.WriteLine(number); // 0

一方、int.Parse(null)は例外になります。入力値の性質や、変換失敗時にどのように処理したいかを考えて使い分けましょう。

7-4. キャストでエラーが起きるケース

参照型のキャストでは、実際のオブジェクトが変換先の型と互換性を持たない場合、InvalidCastExceptionが発生します。

object value = "123";

// 実行時エラー// int number = (int)value;

この例でvalueが参照しているのは文字列です。文字列の内容が数字であっても、文字列オブジェクトをintへ直接キャストすることはできません。

数値へ変換するには、ParseTryParseを使用します。

object value = "123";

if (value is string text &&int.TryParse(text, out int number)){Console.WriteLine(number);}

安全に型を確認するには、isパターンを利用できます。

if (value is string message){Console.WriteLine(message);}

参照型に対しては、as演算子を利用する方法もあります。

object value = "こんにちは";string? text = value as string;

if (text != null){Console.WriteLine(text);}

8. nullable型とnull許容参照型

8-1. nullable型とは

通常の値型にはnullを代入できません。

int score = 100;

// コンパイルエラー// score = null;

値型にnullを許可する仕組みが、nullable値型です。

int? score = null;

nullable値型は、「値が存在する状態」と「値が存在しない状態」の両方を表現できます。

例えば、未入力の年齢や、まだ確定していない点数を表す場合に利用できます。

8-2. int?のような書き方

int?は、Nullable<int>の短縮表記です。

int? age = null;Nullable<int> score = null;

どちらも同じ意味です。

値が入っているかどうかは、HasValueで確認できます。

int? number = 10;

if (number.HasValue){Console.WriteLine(number.Value);}

通常は、パターンマッチングを使うと簡潔に記述できます。

if (number is int value){Console.WriteLine(value);}

null合体演算子??を使えば、nullだった場合の既定値を指定できます。

int? input = null;int result = input ?? 0;

Console.WriteLine(result); // 0

8-3. null許容参照型とは

null許容参照型は、参照型の変数がnullになる可能性をコード上で明示する仕組みです。

nullable参照型が有効な環境では、次の宣言は「原則としてnullを入れない文字列」を表します。

string name = "田中";

次の宣言は、nullになる可能性がある文字列を表します。

string? middleName = null;

string?は、stringとは別の実行時型になるわけではありません。コンパイラがnullの可能性を解析し、危険なコードに警告を出すための注釈です。

string? text = null;

// 警告が出る可能性がある// Console.WriteLine(text.Length);

8-4. nullチェックの基本

基本的なnullチェックは、if文を使う方法です。

string? text = GetText();

if (text != null){Console.WriteLine(text.Length);}

is not nullを使うこともできます。

if (text is not null){Console.WriteLine(text.Length);}

null条件演算子?.を使うと、変数がnullでない場合だけメンバーへアクセスできます。

int? length = text?.Length;

null合体演算子??を組み合わせることもできます。

int length = text?.Length ?? 0;

null抑制演算子!を使うと、コンパイラの警告を抑制できます。

Console.WriteLine(text!.Length);

ただし、!を付けても実際の値がnullでなくなるわけではありません。値が本当にnullだった場合は、実行時エラーになる可能性があります。安全性を確認できる場面に限って使用しましょう。

9. boxingとunboxingとは

9-1. boxingの意味

boxingとは、値型の値をobject型や、その値型が実装しているインターフェース型として扱うための変換です。

int number = 100;object value = number;

この代入では、intの値がobjectとして扱える形になります。これがboxingです。

boxingは暗黙的に行われます。

bool flag = true;object boxedFlag = flag;

9-2. unboxingの意味

unboxingとは、boxingされた値を元の値型として取り出す処理です。

object value = 100;int number = (int)value;

Console.WriteLine(number);

unboxingでは、明示的なキャストが必要です。

また、実際にboxingされた型と、取り出す型が一致している必要があります。

object value = 100;

// intとしてboxingされた値をlongへ直接unboxingできない// long number = (long)value;

intとしてboxingされた値をlongにしたい場合は、まずintとして取り出してから変換します。

object value = 100;

int intValue = (int)value;long longValue = intValue;

9-3. object型との関係

object型は、値型と参照型の両方を受け取れます。

object value1 = 10;object value2 = "C#";

value1では、値型であるintがboxingされます。

一方、value2stringは参照型なので、値型のboxingとは異なり、文字列オブジェクトへの参照がobject型として扱われます。

objectから値を取り出す場合は、型を確認するのが安全です。

object value = 10;

if (value is int number){Console.WriteLine(number);}

9-4. パフォーマンス面の注意点

boxingでは、値をobjectとして扱うための処理やメモリ確保が発生する場合があります。boxingとunboxingを大量に繰り返すと、パフォーマンスやメモリ使用量に影響する可能性があります。

例えば、非ジェネリックなコレクションに値型を保存すると、boxingが発生します。

System.Collections.ArrayList list = new();list.Add(10);

現在のC#では、ジェネリックコレクションを使うのが一般的です。

List<int> numbers = new();numbers.Add(10);

List<int>では要素をintとして扱えるため、通常はobjectを介したboxingを避けられ、型安全性も高まります。

ただし、小規模な処理でboxingを過剰に恐れる必要はありません。パフォーマンス上の問題が確認された場合に、測定結果を基に見直すことが重要です。

10. C#の型を使い分けるポイント

10-1. 数値を扱うときの型の選び方

一般的な整数には、まずintを選ぶとよいでしょう。

int count = 100;

intの範囲を超える可能性がある場合は、longを使用します。

long totalBytes = 5_000_000_000L;

一般的な小数計算には、doubleがよく使われます。

double average = 82.5;

金額や会計のように10進数としての精度が重要な処理には、decimalを使用します。

decimal totalPrice = 1980.50M;

データサイズを小さくしたいという理由だけで、安易にbyteshortを選ぶ必要はありません。計算時の変換や範囲超過によって、かえってコードが複雑になることがあります。

10-2. 文字列を扱うときの型の選び方

1文字だけを扱う場合はcharを使用します。

char answer = 'A';

名前や文章など、複数の文字を扱う場合はstringを使用します。

string message = "C#を勉強しています";

文字列を繰り返し連結する処理では、stringの不変性に注意が必要です。

string result = "";

for (int i = 0; i < 1000; i++){result += i;}

このような処理では、多数の新しい文字列が作成される可能性があります。繰り返し連結する場合は、StringBuilderが適していることがあります。

using System.Text;

StringBuilder builder = new();

for (int i = 0; i < 1000; i++){builder.Append(i);}

string result = builder.ToString();

10-3. 金額計算でdecimalを使う理由

floatdoubleは、2進数で小数を表現します。そのため、10進数では単純に見える値でも、内部では近似値になる場合があります。

double value = 0.1 + 0.2;Console.WriteLine(value);

金額計算では、小さな誤差でも集計結果や請求額に影響する可能性があります。

decimalは10進数の計算に適した型であり、金額や税率を扱う処理でよく使用されます。

decimal price = 1000M;decimal taxRate = 0.1M;decimal total = price + price * taxRate;

Console.WriteLine(total); // 1100.0

ただし、decimalにも扱える範囲と精度の上限があります。また、丸め方法については業務ルールを明確にする必要があります。

decimal rounded = Math.Round(10.555M,2,MidpointRounding.AwayFromZero);

10-4. 型選びでよくある失敗例

よくある失敗の一つは、すべての値をstringで管理することです。

string age = "20";string price = "1980";

文字列のままでは、数値計算のたびに変換が必要になります。年齢にはint、価格にはdecimalなど、データの意味に合った型を選びましょう。

int age = 20;decimal price = 1980M;

もう一つは、大きな値を扱う可能性があるのにintを使用することです。

int fileSize = 2_000_000_000;

将来的にintの上限を超える可能性がある場合は、longを検討します。

また、金額にdoubleを使用したり、nullになる可能性を考えずに参照型へアクセスしたりすることも、よくある失敗です。

型を選ぶときは、次の点を確認しましょう。

  • 値の範囲はどれくらいか

  • 小数の精度はどれくらい必要か

  • nullになる可能性があるか

  • 値として扱うか、同一性を持つオブジェクトとして扱うか

  • 他のシステムやデータベースの型と整合するか

11. 初心者が覚えておきたいC#の型の用語

11-1. class

classは、データと処理をまとめた参照型を定義するためのキーワードです。

class Car{public string Name { get; set; } = "";public int Speed { get; set; }

public void Run(){Console.WriteLine($"{Name}が時速{Speed}kmで走っています");}

}

クラスからオブジェクトを作成するには、newを使用します。

Car car = new Car{Name = "サンプルカー",Speed = 60};

car.Run();

クラスは、継承やポリモーフィズムを利用した設計にも使えます。

11-2. struct

structは、独自の値型を定義するためのキーワードです。

struct Coordinate{public double Latitude { get; }public double Longitude { get; }

public Coordinate(double latitude, double longitude){Latitude = latitude;Longitude = longitude;}

}

構造体は、小さく、値として扱うのが自然なデータに適しています。

Coordinate location = new Coordinate(35.6812, 139.7671);

ただし、構造体を大きくしすぎると、コピーのコストが増える可能性があります。

11-3. enum

enumは、関連する名前付き定数を定義する値型です。

enum PaymentMethod{Cash,CreditCard,BankTransfer}
PaymentMethod method = PaymentMethod.CreditCard;

switch (method){case PaymentMethod.Cash:Console.WriteLine("現金払い");break;

case PaymentMethod.CreditCard:Console.WriteLine("クレジットカード払い");break;case PaymentMethod.BankTransfer:Console.WriteLine("銀行振込");break;

}

意味のない数値や文字列を直接使うよりも、入力できる値を限定しやすく、コードの意図も伝わりやすくなります。

11-4. interface

interfaceは、型が備えるべきメンバーの契約を定義します。

interface ILogger{void Log(string message);}

クラスはインターフェースを実装できます。

class ConsoleLogger : ILogger{public void Log(string message){Console.WriteLine(message);}}

インターフェース型の変数には、そのインターフェースを実装したオブジェクトを代入できます。

ILogger logger = new ConsoleLogger();logger.Log("処理を開始しました");

具体的なクラスではなくインターフェースに依存することで、実装を差し替えやすい設計にできます。

11-5. generics

ジェネリックとは、具体的な型を後から指定できる仕組みです。

代表例がList<T>です。

List<int> numbers = new();numbers.Add(10);numbers.Add(20);

文字列を保存したい場合は、List<string>を使用します。

List<string> names = new();names.Add("田中");names.Add("佐藤");

独自のジェネリッククラスも作成できます。

class Box<T>{public T? Value { get; set; }}
Box<int> numberBox = new Box<int>();numberBox.Value = 100;

Box<string> textBox = new Box<string>();textBox.Value = "C#";

ジェネリックを使うと、型安全性を維持しながら、さまざまな型に対応する再利用可能なコードを作成できます。

12. C#の型に関するよくある質問

12-1. intとlongはどちらを使うべき?

通常の整数には、基本的にintを使用します。

int age = 30;int quantity = 100;

intの範囲を超える可能性がある場合は、longを使用します。

long population = 8_000_000_000L;

ファイルサイズ、アクセス件数、時刻を表す大きな数値などでは、longが必要になる場合があります。

また、外部APIやデータベースの型が決まっている場合は、その仕様との整合性も考慮しましょう。

12-2. stringは値型?参照型?

stringは参照型です。

string name = "田中";

ただし、stringはイミュータブルであり、作成後の文字列内容を直接変更できません。

string first = "Hello";string second = first;

second = "World";

Console.WriteLine(first); // HelloConsole.WriteLine(second); // World

この動作だけを見ると値型のように感じることがありますが、stringはあくまで参照型です。

文字列を変更する処理では、基本的に新しい文字列が作成されます。

12-3. varを使うと型はなくなる?

varを使っても型はなくなりません。

var number = 10;

この変数は、コンパイル時にint型として決定されます。その後、文字列など別の型の値を代入することはできません。

var number = 10;

// コンパイルエラー// number = "10";

varは型を省略しているのではなく、右辺からコンパイラに型を推論させる書き方です。

実行時に型が決まるdynamicとは異なります。

dynamic value = 10;value = "文字列";

dynamicでは型に関する一部の確認が実行時まで遅れるため、必要な場面に限定して使用するのが一般的です。

12-4. object型は何でも入れられる?

C#の型は、基本的にobjectとして扱えます。

object value1 = 100;object value2 = "C#";object value3 = true;

ただし、何でも自由に同じ操作ができるわけではありません。object型の変数から元の型固有の機能を使うには、型の確認や変換が必要です。

object value = "こんにちは";

if (value is string text){Console.WriteLine(text.Length);}

また、値型をobjectへ代入するとboxingが発生する場合があります。

型があらかじめ分かっている場合は、objectを多用するよりも、具体的な型やジェネリック型を使うほうが安全です。

12-5. 値型と参照型はどちらが速い?

値型と参照型のどちらが常に速い、とは言い切れません。

値型は小さなデータであれば効率よく扱える場合がありますが、サイズが大きいとコピーのコストが増えます。

参照型はオブジェクト自体を毎回コピーせず、参照を受け渡せますが、オブジェクトの生成やガベージコレクションが処理コストに影響する場合があります。

パフォーマンスは、次のような条件によって変わります。

  • 型のサイズ

  • オブジェクトを作成する回数

  • 代入やメソッド呼び出しの回数

  • boxingの有無

  • メモリアクセスの方法

  • 実行環境による最適化

  • ガベージコレクションの発生状況

型を選ぶ際は、速度だけでなく、データの意味と設計上の扱い方を優先しましょう。

値として独立して扱いたい小さなデータには値型、同一性を持ち、複数の場所から共有したいオブジェクトには参照型が適しています。性能が重要な場合は、推測だけで判断せず、実際に計測することが大切です。

まとめ

C#の型とは、変数が扱うデータの種類や、実行できる操作を決める仕組みです。型を正しく理解することで、コンパイル時に間違いを発見しやすくなり、安全で読みやすいプログラムを作成できます。

C#の型は、大きく値型・参照型・ポインター型に分類されます。初心者が特に押さえておきたいのは、値型と参照型の違いです。

値型はデータそのものを保持し、代入時には値がコピーされます。一方、参照型はオブジェクトへの参照を保持し、代入時には参照がコピーされます。

基本的な型の選び方としては、一般的な整数にはint、大きな整数にはlong、一般的な小数にはdouble、金額にはdecimal、文字列にはstringを使用します。

また、次のポイントも重要です。

  • varを使っても型はコンパイル時に確定する

  • stringは参照型だがイミュータブルである

  • 値型にnullを許可するにはint?などを使う

  • null許容参照型を使うと、null参照の危険性を見つけやすくなる

  • 値型をobjectとして扱うとboxingが発生する場合がある

  • 文字列から数値への変換には、状況に応じてTryParseなどを使う

  • 値型と参照型の性能は、使用方法やデータサイズによって変わる

最初からすべてを暗記する必要はありません。まずはintdoubledecimalboolcharstringの使い方を覚え、次に値型と参照型の代入動作をコードで確認すると理解しやすくなります。

C#の型は、変数だけでなく、クラス設計、メソッドの引数、ジェネリック、null安全性など、あらゆる機能の基礎です。実際に短いコードを書きながら、それぞれの型がどのように動作するかを確かめていきましょう。