第11章 値型と参照型
この章の目的
Section titled “この章の目的”この章では、C# の型を理解するうえで重要な 値型 と 参照型 について学習します。
これまでに、次のような型を使ってきました。
int score = 80;bool isPassed = true;string employeeName = "山田二郎";
Employee employee = new Employee(1001, "山田二郎", "総務");一見、どれも同じように「変数に値を入れている」ように見えます。 しかし、C# では型によって 値の扱われ方 が異なります。
値型 値そのものを変数が持つ
参照型 オブジェクトの「場所」を変数が指し示すこの違いは、後のオブジェクト指向や DB 接続で重要になります。 特に、データベースから取得したデータには NULL が混じることがあり、その NULL を C# 側でどう扱うかが課題になります。
この章で学ぶ内容:
構造体(struct)列挙型(enum)値型と参照型の違いnull とは何かnull許容型(int?、decimal? など)この章でできるようになること
Section titled “この章でできるようになること”この章を終えると、次のことができるようになります。
- 値型と参照型の違いをおおまかに説明できる
- 構造体とは何かを説明できる
- 簡単な構造体を定義できる
- 列挙型とは何かを説明できる
- 列挙型を使って選択肢を分かりやすく表現できる
- 値型を別の変数に代入したときの動きを説明できる
- 参照型を別の変数に代入したときの動きを説明できる
- メソッドに値型・参照型を渡したときの違いを説明できる
nullが「参照先がない」状態を表すことを説明できるnullによるエラーを防ぐ書き方ができるint?やdecimal?のような null 許容値型を使える- DB の NULL と C# の null の関係をおおまかに説明できる
本章で使用する環境
Section titled “本章で使用する環境”| 項目 | 内容 |
|---|---|
| 開発環境 | Visual Studio 2022 |
| プロジェクト種類 | コンソール アプリ |
| 対象フレームワーク | .NET 8 |
| ソリューション名 | Chapter11 |
| プロジェクト名 | Ch11_ValueReferenceNull |
csproj の Nullable は disable に変更してください
プロジェクト作成後、
Ch11_ValueReferenceNull.csprojを開き、<Nullable>disable</Nullable>に変更してください。詳しい手順は、第 1 章「1-1 プロジェクトを作成する」を参照してください。
作業前チェック
Section titled “作業前チェック”作業を始める前に、次の内容を確認してください。
-
int・double・bool・stringを使える - クラス・プロパティ・コンストラクター・メソッドを書ける(第 7〜10 章)
-
if文・switch文で条件分岐できる - 第 10 章の内容を Git に提出済みである
11-1 構造体
Section titled “11-1 構造体”構造体(struct)は、種類の違うデータを 1 つにまとめて扱えるようにした 型です。
たとえば「名前(string)」と「年齢(int)」のように、別々の型の値を 1 つのまとまりとして持てます。
クラスと似ていますが、値型(代入すると値そのものがコピーされる)であることが大きな違いです。
実は、これまで使ってきた次の型も、すべて構造体の仲間です。
| 型 | 用途 |
|---|---|
int、double、decimal | 数値 |
bool | 真偽値 |
char | 1 文字 |
DateTime | 日付・時刻 |
int や bool のように 1 つの値だけを持つシンプルな構造体もあれば、DateTime のように複数のデータ(年・月・日・時・分・秒)をまとめた構造体もあります。
つまり、皆さんはすでに構造体を毎日使っています。
自分で構造体を定義する
Section titled “自分で構造体を定義する”社員番号を「部門記号(string)」と「連番(int)」の 2 つで表す構造体を作ってみます。
型の違う 2 つのデータ(string と int)を 1 つにまとめている 点に注目してください。これが構造体らしい使い方です。
EmployeeId.cs:
namespace Ch11_ValueReferenceNull;
struct EmployeeId{ public string Prefix { get; set; } // 部門記号(例:EMP) public int Number { get; set; } // 連番(例:1001)
public EmployeeId(string prefix, int number) { Prefix = prefix; Number = number; }
public string ToFullId() { return $"{Prefix}{Number}"; }}Program.cs:
namespace Ch11_ValueReferenceNull;
internal class Program{ static void Main(string[] args) { EmployeeId id = new EmployeeId("EMP", 1001);
Console.WriteLine($"部門記号:{id.Prefix}"); Console.WriteLine($"連番:{id.Number}"); Console.WriteLine($"社員番号:{id.ToFullId()}"); }}実行結果:
部門記号:EMP連番:1001社員番号:EMP1001構造体は struct キーワードで定義します。Prefix(string)と Number(int)という 型の違うデータを 1 つにまとめ、ToFullId() のようなメソッドも持てます。プロパティ・コンストラクター・メソッドを持てる点はクラスと同じです。
構造体とクラスの主な違い
Section titled “構造体とクラスの主な違い”| 観点 | 構造体(struct) | クラス(class) |
|---|---|---|
| 型の分類 | 値型 | 参照型 |
| 代入時 | 値そのものがコピーされる | 参照(場所)がコピーされる |
| 用途 | 小さな値のまとまり(座標、ID、日付など) | 機能や状態を持つもの(社員、商品、注文など) |
この章では、構造体は 「小さな値のまとまりを表す型」 くらいの理解で十分です。
業務で自分から構造体を作る場面は多くありませんが、DateTime のような既製の構造体は頻繁に使います。
11-2 列挙型
Section titled “11-2 列挙型”列挙型(enum)は、決まった選択肢を名前で表す ための型です。
たとえば、社員の在籍状態を文字列で管理すると、表記揺れが起きやすくなります。
string status1 = "在籍中";string status2 = "在籍"; // ← 表記違いstring status3 = "在籍中 "; // ← 末尾に空白if (status == "在籍中") で判定するつもりが、別の表記が混ざってマッチしない、ということが起きます。
選択肢が決まっている 場合は、列挙型を使うと安全です。
EmployeeStatus 列挙型を作る
Section titled “EmployeeStatus 列挙型を作る”EmployeeStatus.cs:
namespace Ch11_ValueReferenceNull;
enum EmployeeStatus{ Active, // 在籍中 Retired, // 退職 LeaveOfAbsence // 休職中}Program.cs:
namespace Ch11_ValueReferenceNull;
internal class Program{ static void Main(string[] args) { EmployeeStatus status = EmployeeStatus.Active;
if (status == EmployeeStatus.Active) { Console.WriteLine("在籍中です。"); } else { Console.WriteLine("在籍中ではありません。"); } }}実行結果:
在籍中です。EmployeeStatus.Active のように クラス名.値 の形でアクセスします。
Active・Retired・LeaveOfAbsence 以外の値を代入しようとするとコンパイルエラーになるため、決まった選択肢だけが扱われます。
switch 文との組み合わせ
Section titled “switch 文との組み合わせ”列挙型は switch 文と相性がよいです。
namespace Ch11_ValueReferenceNull;
internal class Program{ static void Main(string[] args) { EmployeeStatus status = EmployeeStatus.LeaveOfAbsence;
switch (status) { case EmployeeStatus.Active: Console.WriteLine("在籍中です。"); break; case EmployeeStatus.Retired: Console.WriteLine("退職しています。"); break; case EmployeeStatus.LeaveOfAbsence: Console.WriteLine("休職中です。"); break; } }}実行結果:
休職中です。case の値も列挙型なので、入力ミスがあればコンパイル時に気付けます。
列挙型を表示すると英語名になる
Section titled “列挙型を表示すると英語名になる”列挙型の値をそのまま Console.WriteLine に渡すと、英語名 が出力されます。
Console.WriteLine(EmployeeStatus.Active); // → Active画面表示には日本語が必要なことが多いので、変換用のメソッドを用意します。
たとえば Employee クラスに Status プロパティを持たせ、表示用文字列を返すメソッドを足します。
Employee.cs:
namespace Ch11_ValueReferenceNull;
class Employee{ public int EmployeeId { get; set; } public string EmployeeName { get; set; } public EmployeeStatus Status { get; set; }
public Employee(int employeeId, string employeeName, EmployeeStatus status) { EmployeeId = employeeId; EmployeeName = employeeName; Status = status; }
public string GetStatusText() { switch (Status) { case EmployeeStatus.Active: return "在籍中"; case EmployeeStatus.Retired: return "退職"; case EmployeeStatus.LeaveOfAbsence: return "休職中"; default: return "不明"; } }}Program.cs:
namespace Ch11_ValueReferenceNull;
internal class Program{ static void Main(string[] args) { Employee employee = new Employee(1001, "山田二郎", EmployeeStatus.Active);
Console.WriteLine($"社員番号:{employee.EmployeeId}"); Console.WriteLine($"氏名:{employee.EmployeeName}"); Console.WriteLine($"状態:{employee.GetStatusText()}"); }}実行結果:
社員番号:1001氏名:山田二郎状態:在籍中11-3 値型と参照型の動きの違い
Section titled “11-3 値型と参照型の動きの違い”おおまかな分類
Section titled “おおまかな分類”C# の型は、大きく次のように分けられます。
| 種類 | 代表例 | 値の扱い方 |
|---|---|---|
| 値型 | int、double、bool、DateTime、構造体、列挙型 | 値そのものを変数が持つ |
| 参照型 | string、配列、クラス(自作含む)、List<T> | オブジェクトの場所を変数が指す |
この違いは、変数を別の変数に代入したときに最もはっきり現れます。
補足:
stringは特別な参照型
stringは参照型ですが、文字列を変更すると常に新しい文字列が作られるため、値型のように振る舞います。 この章では深追いせず、「stringは普段は値型のように扱えば OK」と覚えておきましょう。
値型の代入(int の場合)
Section titled “値型の代入(int の場合)”int 型で代入の動きを確認します。
namespace Ch11_ValueReferenceNull;
internal class Program{ static void Main(string[] args) { int numberA = 10; int numberB = numberA; // numberA の値 10 をコピー
numberA = 99; // numberA だけ変更
Console.WriteLine($"numberA:{numberA}"); Console.WriteLine($"numberB:{numberB}"); }}実行結果:
numberA:99numberB:10int numberB = numberA; の時点で 値 10 がコピー されます。
その後 numberA を変更しても、numberB には影響しません。
numberA = 10 → numberB = 10 (10 をコピー)numberA = 99 → numberB = 10 (独立しているのでそのまま)参照型の代入(クラスの場合)
Section titled “参照型の代入(クラスの場合)”同じことをクラスで試します。
Employee.cs(シンプル版):
namespace Ch11_ValueReferenceNull;
class Employee{ public int EmployeeId { get; set; } public string EmployeeName { get; set; }
public Employee(int employeeId, string employeeName) { EmployeeId = employeeId; EmployeeName = employeeName; }}Program.cs:
namespace Ch11_ValueReferenceNull;
internal class Program{ static void Main(string[] args) { Employee employeeA = new Employee(1001, "山田二郎"); Employee employeeB = employeeA; // 同じオブジェクトを共有
employeeA.EmployeeName = "佐藤昭夫";
Console.WriteLine($"employeeA:{employeeA.EmployeeName}"); Console.WriteLine($"employeeB:{employeeB.EmployeeName}"); }}実行結果:
employeeA:佐藤昭夫employeeB:佐藤昭夫Employee employeeB = employeeA; では、オブジェクトの コピー ではなく、オブジェクトの 場所(参照) がコピーされます。
つまり、employeeA と employeeB は 同じ 1 つのオブジェクト を別の名前で見ている状態です。
- 矢印は 「変数が指している先」 を表します
employeeAとemployeeBの 2 本の矢印が 同じ 1 つのオブジェクト に向いている状態です- どちらの変数経由でも、同じオブジェクトに変更を加えることになります(=だから片方を変えるともう片方にも見える)
別々のオブジェクトにしたいときは new
Section titled “別々のオブジェクトにしたいときは new”別のオブジェクトとして扱いたい場合は、それぞれ new します。
Employee employeeA = new Employee(1001, "山田二郎");Employee employeeB = new Employee(1001, "山田二郎"); // 別オブジェクト
employeeA.EmployeeName = "佐藤昭夫";
Console.WriteLine($"employeeA:{employeeA.EmployeeName}");Console.WriteLine($"employeeB:{employeeB.EmployeeName}");実行結果:
employeeA:佐藤昭夫employeeB:山田二郎中身が同じ値でも、new で別々に作ったオブジェクトは 別物 です。
メソッドに値型を渡す
Section titled “メソッドに値型を渡す”値型をメソッドに渡したときも、値はコピーされます。
namespace Ch11_ValueReferenceNull;
internal class Program{ static void Main(string[] args) { int number = 10;
ChangeNumber(number);
Console.WriteLine(number); }
static void ChangeNumber(int value) { value = 99; }}実行結果:
10メソッド内で value = 99; としても、呼び出し元の number には影響しません。
メソッドに参照型を渡す
Section titled “メソッドに参照型を渡す”一方、参照型では呼び出し元のオブジェクトに 影響します。
namespace Ch11_ValueReferenceNull;
internal class Program{ static void Main(string[] args) { Employee employee = new Employee(1001, "山田二郎");
ChangeEmployeeName(employee);
Console.WriteLine(employee.EmployeeName); }
static void ChangeEmployeeName(Employee target) { target.EmployeeName = "佐藤昭夫"; }}実行結果:
佐藤昭夫target と employee は同じオブジェクトを指しているため、target.EmployeeName を変えると、呼び出し元の employee.EmployeeName も変わって見えます。
値型と参照型の整理
Section titled “値型と参照型の整理”| 観点 | 値型 | 参照型 |
|---|---|---|
| 代表例 | int、bool、DateTime、構造体 | クラス、配列、List<T> |
| 代入で何がコピーされる? | 値そのもの | 参照(場所) |
| 別の変数への変更の影響 | 影響しない | 同じオブジェクトを指していれば影響する |
| メソッド呼び出し時の挙動 | 値がコピーされて渡される | 参照がコピーされて渡される(中身は共有) |
null を入れられる? | 通常入れられない | 入れられる |
11-4 参照先がないことを表す null
Section titled “11-4 参照先がないことを表す null”null とは
Section titled “null とは”null は、参照型の変数が 何のオブジェクトも指していない 状態を表す特別な値です。
Employee employee = null;employee という変数はありますが、まだ具体的な Employee オブジェクトを指していません。
null の変数を使うとエラーになる
Section titled “null の変数を使うとエラーになる”次のコードは 実行時に エラーになります。
Employee employee = null;
Console.WriteLine(employee.EmployeeName); // ← 実行時エラーemployee は何も指していないので、.EmployeeName を取り出そうとしても対象が存在しません。
このエラーは NullReferenceException という名前で、C# でもっともよく遭遇するエラーの 1 つです。
employee がオブジェクトを指していない ↓employee.EmployeeName を取ろうとする ↓そもそも EmployeeName を持つ「もの」がない ↓NullReferenceExceptionnull かどうか確認してから使う
Section titled “null かどうか確認してから使う”null の可能性がある変数は、使う前に if で確認します。
namespace Ch11_ValueReferenceNull;
internal class Program{ static void Main(string[] args) { Employee employee = FindEmployee(9999);
if (employee == null) { Console.WriteLine("社員が見つかりませんでした。"); } else { Console.WriteLine($"見つかりました:{employee.EmployeeName}"); } }
static Employee FindEmployee(int employeeId) { if (employeeId == 1001) { return new Employee(1001, "山田二郎"); }
return null; // 見つからなかった場合 }}実行結果:
社員が見つかりませんでした。「検索結果がないこと」を null で表すのは、C# でよくあるパターンです。
補足:null になり得るメソッドの目印
Nullable enableが有効なプロジェクトでは、null を返す可能性があるメソッドの戻り値をEmployee?のように?付きで宣言します。 本研修はNullable disableのため?は使いませんが、現場のコードでEmployee?を見たら「null になるかもしれない」というサインだと読み取りましょう。
11-5 値型と null 許容型
Section titled “11-5 値型と null 許容型”値型には通常 null を入れられない
Section titled “値型には通常 null を入れられない”int や DateTime などの値型には、通常 null を代入できません。
int salary = null; // ← コンパイルエラーDateTime hireDate = null; // ← コンパイルエラー値型は「常に何らかの値を持つ」型だからです。
null 許容値型(int? など)
Section titled “null 許容値型(int? など)”値型でも、? を付けると null を入れられる型 になります。
int? salary = null;decimal? commission = null;DateTime? hireDate = null;bool? isManager = null;これを null 許容値型 と呼びます。
| 通常の値型 | null 許容値型 |
|---|---|
int | int? |
decimal | decimal? |
DateTime | DateTime? |
bool | bool? |
int? は内部的には「int の値、または『値なし』」のどちらかを保持する仕組みです。
int? を使う
Section titled “int? を使う”namespace Ch11_ValueReferenceNull;
internal class Program{ static void Main(string[] args) { int? salary = null;
if (salary == null) { Console.WriteLine("給与は未設定です。"); } else { Console.WriteLine($"給与:{salary}円"); } }}実行結果:
給与は未設定です。int? を使うことで、値が決まっていない状態 を表現できます。
HasValue と Value
Section titled “HasValue と Value”null 許容値型には、専用のプロパティがあります。
| プロパティ | 内容 |
|---|---|
HasValue | 値があれば true、null なら false |
Value | 値を取り出す(null の状態で呼ぶとエラー) |
int? salary = 500000;
if (salary.HasValue){ Console.WriteLine($"給与:{salary.Value}円");}else{ Console.WriteLine("給与は未設定です。");}実行結果:
給与:500000円注意:
Valueは null のときに呼ぶとエラー
salary.Valueは、salaryがnullの状態で呼び出すとInvalidOperationExceptionになります。 必ずHasValueを確認してから使うか、==でnullチェックしてから使ってください。
null 合体演算子 ??
Section titled “null 合体演算子 ??”?? を使うと、null だった場合の代わりの値 を簡潔に指定できます。
int? salary = null;
int displaySalary = salary ?? 0;
Console.WriteLine($"表示用給与:{displaySalary}円");実行結果:
表示用給与:0円salary ?? 0 ↓salary が null でなければ salary の値salary が null なら 0if 文と比べて短く書けます。
| 書き方 | 意味 |
|---|---|
salary ?? 0 | salary が null なら 0、それ以外は salary |
userName ?? "ゲスト" | userName が null なら “ゲスト” |
hireDate ?? DateTime.Today | hireDate が null なら今日の日付 |
DB の NULL と C# の null
Section titled “DB の NULL と C# の null”データベースでは、値が入っていないことを NULL で表します。
SQL 研修で扱った Oracle の employees テーブルでも、commission(歩合)が NULL の社員がいたはずです。第 16 章以降で扱う SQLServer のテーブルでも、email や salary のような列は NULL を持てるよう設計されています。
C# で DB のデータを受け取るとき、NULL になりうる列は null 許容値型 で受け取ります。
class Employee{ public int EmployeeId { get; set; } public string EmployeeName { get; set; } public int JobId { get; set; } public int? ManagerId { get; set; } // ← NULL あり public DateTime HireDate { get; set; } public decimal Salary { get; set; } public decimal? Commission { get; set; } // ← NULL あり public int DepartmentId { get; set; }}NULL の可能性がある列を int?・decimal?・DateTime? で表しておくと、SQL の NULL を C# の null として自然に受け取れます。
詳しい使い方は、第 17 章「C# からデータベースを操作する」以降で扱います。
この章で覚えておきたいのは次のことです。
DB の NULL → C# では null として扱う
値型で null を持たせたい → int? や decimal? を使う
参照型はそのまま null を持てる → 使う前に null チェックするよくあるつまずき
Section titled “よくあるつまずき”| つまずき | 原因 | 対応 |
|---|---|---|
| 構造体とクラスの違いが分からない | 見た目が似ている | 「構造体 = 値型、クラス = 参照型」 |
| 列挙型を使う意味が分からない | 文字列でも書ける | 決まった選択肢を安全に扱うために使う |
| 値型代入の動きを間違える | 値がコピーされる感覚がない | int の代入で確認する |
| 参照型代入の動きを間違える | 同じオブジェクトを指す感覚がない | Employee で Name を変えて確認する |
| メソッド内で値を変えたのに反映されない | 値型はコピーが渡される | 値型と参照型の違いを確認する |
null の意味が分からない | 空文字や 0 と混同している | null は「参照先がない」状態 |
null の変数を使ってエラーになる | オブジェクトがないのにプロパティを呼んでいる | if (変数 == null) で先に確認 |
int salary = null; がエラー | int は値型で通常 null を持てない | int? を使う |
salary.Value でエラー | null のとき Value を呼んでいる | HasValue または ?? を先に確認 |
| 列挙型の表示が英語のまま | Console.WriteLine(status) は英語名を返す | 変換メソッドで日本語に変える |
学んだことチェック
Section titled “学んだことチェック”- 値型と参照型の違いを説明できる
-
structで構造体を定義できる -
DateTimeも構造体であることを説明できる -
enumで列挙型を定義できる - 列挙型の値を
switch文で判定できる - 列挙型を日本語表示するメソッドを書ける
- 値型の代入結果を説明できる
- 参照型の代入結果を説明できる
-
newすると別オブジェクトになることを説明できる - メソッドに値型・参照型を渡したときの違いを説明できる
-
nullが何を表すか説明できる -
nullチェックをifで書ける -
int?、decimal?、DateTime?のような null 許容値型を使える -
HasValue・Valueを使える -
??を使って null 時の代替値を指定できる
研修の進め方によっては、隣の人またはチーム内で説明確認を行います。
次の内容を、自分の言葉で説明してください。
- 値型と参照型の違いは何ですか。
int numberB = numberA;のあとnumberAを変更してもnumberBが変わらないのはなぜですか。Employee employeeB = employeeA;のあとemployeeA.EmployeeNameを変更すると、employeeB.EmployeeNameも変わるのはなぜですか。nullとは何ですか。intにnullを入れたい場合、どう書きますか。??演算子はどのようなときに使いますか。- DB の NULL を C# で扱うとき、どんな型を使いますか。
説明するときは、完全な答えでなくても構いません。 自分の言葉で説明しようとすることが大切です。
この章の演習課題に取り組みます。
本章では タイマー方式 を試験導入します。次の 3 段階で進めてください。
| 段階 | 時間 | 内容 |
|---|---|---|
| ① 準備 | 10 分(目安) | 上の「ペア確認」と、これから取り組む課題(仕様)の読み込み。ペアや講師に質問してよい |
| ② ソロ作業 | 25 分(タイマーで計測) | 一人で課題に取り組む。タイマーが鳴ったら、完成・未完成にかかわらず作業を止めて提出する |
| ③ チーム時間 | 講師が指定する発表開始時刻まで | チーム内でコードレビューを行い、発表者を決める。実装の続行も可。時間配分はチームで管理する |
第 11 章の必須課題は 本文サンプルの動きを観察する型 が中心で重くはありませんが、別ファイルでクラス・構造体・列挙型を作る作業が課題ごとに発生します。ソロ作業は 25 分 をとっています。準備フェーズの終わりに講師が号令をかけ、そこからタイマーを開始します。 評価対象はタイマー時点で提出されたコードです(タイマー後に書き足した分は評価には含まれません)。 発表開始時刻は厳守 です。チーム時間中も、その時刻が来たら全員手を止めて発表に移ります。
演習の進め方の詳細は、付録A「演習の進め方」 を参照してください。
この章の演習の進め方
Section titled “この章の演習の進め方”課題はソリューション Kadai11 の中に作成してください。
課題ごとに別のプロジェクトを作成 し、指定されたプロジェクト名を使います。
| 課題 | 必須/発展 | プロジェクト名 | 作成する主なファイル |
|---|---|---|---|
| 課題 11-1 | 必須 | Kd11_01_ValueAndReference | Program.cs、Employee.cs |
| 課題 11-2 | 必須 | Kd11_02_EmployeeStatusEnum | Program.cs、EmployeeStatus.cs |
| 課題 11-3 | 必須 | Kd11_03_FindEmployee | Program.cs、Employee.cs |
| 課題 11-4 | 発展 | Kd11_04_PeriodDays | Program.cs、Period.cs |
| 課題 11-5 | 発展 | Kd11_05_NullableSalary | Program.cs |
| 課題 11-6 | 発展 | Kd11_06_EmployeeWithNull | Program.cs、Employee.cs |
フォルダ構成は次のようになります。
Kadai11/ ← 課題用ソリューションフォルダ Kadai11.sln Kd11_01_ValueAndReference/ Kd11_02_EmployeeStatusEnum/ Kd11_03_FindEmployee/ Kd11_04_PeriodDays/ Kd11_05_NullableSalary/ Kd11_06_EmployeeWithNull/補足:ソリューションに複数のプロジェクトを追加する方法
最初の課題で
Kadai11ソリューションとKd11_01_ValueAndReferenceプロジェクトを同時に作成します。2 つ目以降の課題は、ソリューションエクスプローラーで
Kadai11を右クリックし、追加→新しいプロジェクトから追加します。
演習の共通ルール
Section titled “演習の共通ルール”以下は、本章のすべての課題に共通する作業です。各課題の本文には繰り返し書きません。
| 作業 | 内容 | 参照 |
|---|---|---|
| プロジェクト作成時の設定 | 「最上位レベルのステートメントを使用しない」にチェック | 第 1 章 1-1 |
| csproj の編集 | <Nullable>disable</Nullable> に変更 | 第 1 章 1-1 |
namespace の書き方 | ファイルスコープ形式(namespace XXX;)で書く | 第 7 章 冒頭 |
| クラス・構造体・列挙型ファイルの追加 | ソリューションエクスプローラーでプロジェクトを右クリック → 追加 → クラス | 第 7 章 7-7 |
| ファイルを保存して実行 | Ctrl + S で保存 → F5 で実行 | 第 1 章 1-2 |
特に 2 つ目以降のプロジェクト(Kd11_02_EmployeeStatusEnum など)も、新規追加するたびに csproj の Nullable を disable に変更する 必要があります。
クラスファイルを自動生成すると、ブロック形式の namespace プロジェクト名 { ... } で作られます(例:課題 11-1 なら namespace Kd11_01_ValueAndReference { ... })。本章でもファイルスコープ形式に統一するため、生成後に namespace Kd11_01_ValueAndReference; のように ; 形式に書き換えてください。同じプロジェクト内の Program.cs と他のクラス・構造体・列挙型ファイルの namespace は揃えます。
補足:構造体・列挙型ファイルの追加
Visual Studio の「追加 → クラス」テンプレートで作成すると、生成されるファイルは
classで始まります。構造体・列挙型を作る場合は、生成後にclass XXXの部分をstruct XXXまたはenum XXXに書き換えてください。
提出ルール(タイマー方式)
25 分のタイマーが鳴ったら手を止め、
git add→commit→pushを行います。評価対象はタイマー時点のコミットのみです(タイマー後の追加は評価外)。Chapter11 タイマー提出: <どこまで完成> / <詰まったポイント>例:
Chapter11 タイマー提出: 11-1〜11-2完成、11-3は途中 / null 返却の書き方で詰まった
サーバへコピー提出の場合
Git 不調で講師指示があれば、
pushの代わりにKadai11フォルダをサーバへコピーし、提出先に提出メモ.txtを作成します(内容:どこまで完成 / 詰まったポイント)。
タイマー後のチーム時間
発表開始時刻まで、コードレビュー / 発表者選出 / 実装続行(任意・評価外)。時間配分はチームの判断、発表開始時刻は厳守。
まずは、全員が必須課題に取り組んでください。
課題 11-1 値型と参照型の代入を確認する
Section titled “課題 11-1 値型と参照型の代入を確認する”値型と参照型の代入の違いを 1 つのプログラムで確認してください。
処理内容
int numberA = 10;を作成、int numberB = numberA;で代入、その後numberA = 99;に変更numberAとnumberBの値を表示Employee employeeA = new Employee(1001, "山田二郎");を作成、Employee employeeB = employeeA;で代入、その後employeeA.EmployeeName = "佐藤昭夫";に変更employeeA.EmployeeNameとemployeeB.EmployeeNameの値を表示
Employee クラスのプロパティ:
| プロパティ名 | 型 |
|---|---|
EmployeeId | int |
EmployeeName | string |
Employee.cs を別ファイルとして作成し、引数付きコンストラクターを用意してください。
実行結果例:
[値型]numberA:99numberB:10
[参照型]employeeA:佐藤昭夫employeeB:佐藤昭夫条件:
- 値型と参照型で結果が異なることを実行で確認する
Employeeクラスは別ファイルEmployee.csに書く
課題 11-2 EmployeeStatus 列挙型を使う
Section titled “課題 11-2 EmployeeStatus 列挙型を使う”社員の在籍状態を表す列挙型を作成し、switch 文で日本語に変換して表示してください。
EmployeeStatus.cs:
enum EmployeeStatus{ Active, Retired, LeaveOfAbsence}Main メソッドの処理:
- 変数
EmployeeStatus statusに 3 つの値を順番に代入する - それぞれについて
switch文で日本語表示する
実行結果例:
在籍中です。退職しています。休職中です。条件:
enumを使うswitch文を使う- 3 つの状態すべてを試す
課題 11-3 null を返すメソッドと null チェック
Section titled “課題 11-3 null を返すメソッドと null チェック”社員を検索するメソッドを作成し、見つからなかった場合に null を返してください。
仕様
- メソッド名:
FindEmployee(int employeeId) - 戻り値:
Employee - 社員番号
1001のときだけnew Employee(1001, "山田二郎")を返す - それ以外のときは
nullを返す
Main メソッドの処理
FindEmployee(1001)とFindEmployee(9999)の両方を呼び出す- 結果が
nullかどうかをif文で確認し、見つかった場合は氏名を、見つからなかった場合は「社員が見つかりませんでした。」と表示する
実行結果例:
見つかりました:山田二郎社員が見つかりませんでした。条件:
Employeeクラスは別ファイルに書くFindEmployeeメソッドはProgram.csにstaticメソッドとして定義してよい- 戻り値を使う前に必ず
nullチェックする
必須課題が終わった人は、発展課題に取り組んでください。 発展課題からは、仕様だけが提示されます。実装方法は自分で考えてください。
課題 11-4 Period 構造体で勤続日数を計算する
Section titled “課題 11-4 Period 構造体で勤続日数を計算する”期間を表す Period 構造体を自分で設計し、入社日から今日までの 勤続日数 を計算してください。
DateTime も C# 標準の構造体(=値型)です。本課題ではその DateTime を 2 つ束ねた、自作の構造体を作ります。
構造体仕様
- ファイル名:
Period.cs - 構造体名:
Period - プロパティ:
Start(DateTime):開始日End(DateTime):終了日
- コンストラクター:
Period(DateTime start, DateTime end)でStart・Endを初期化する - メソッド:
GetDayCount()を作成し、End - Startの日数をintで返す- ヒント:
DateTime同士の引き算はTimeSpan型になり、.Daysプロパティで日数を取り出せる
- ヒント:
Main メソッドの処理
- 次の社員 2 名の入社日と今日 (
DateTime.Today) からPeriodを 1 件ずつ作成 - それぞれの勤続日数を表示
| 氏名 | 入社日 |
|---|---|
| 山田二郎 | 2001 年 4 月 1 日 |
| 佐々木明子 | 2010 年 10 月 1 日 |
実行結果例(日数は実行日によって変わります):
山田二郎の勤続日数:9183日佐々木明子の勤続日数:5715日条件:
structを使うDateTimeのプロパティ・引き算・TimeSpan.Daysを使う- 構造体を別ファイル
Period.csに書く
課題 11-5 null 許容値型で給与・歩合を扱う
Section titled “課題 11-5 null 許容値型で給与・歩合を扱う”給与と歩合のうち、歩合は未設定(null)になり得るプログラムを作成してください。
Main メソッドの処理
- 次の 2 名分の社員情報を変数で表現する
| 氏名 | salary (int?) | commission (decimal?) |
|---|---|---|
| 山田二郎 | 500000 | null |
| 佐々木明子 | 800000 | 1200000m |
- 1 名ずつ、給与と歩合を表示する
- 給与:
HasValueで確認し、ある場合のみ表示 - 歩合:
??を使って null なら0を代わりに表示する
実行結果例:
山田二郎の給与:500000円山田二郎の歩合:0円佐々木明子の給与:800000円佐々木明子の歩合:1200000円条件:
int?とdecimal?を使うHasValueを使う??を使う
課題 11-6 DB 接続を想定した Employee クラス
Section titled “課題 11-6 DB 接続を想定した Employee クラス”第 16 章以降の DB 接続編で使うことを想定し、null 許容値型を含む Employee クラスを作成してください。
クラス仕様
- ファイル名:
Employee.cs - プロパティ:
| プロパティ名 | 型 | 意味 |
|---|---|---|
EmployeeId | int | 社員番号 |
EmployeeName | string | 氏名 |
HireDate | DateTime | 入社日 |
Salary | decimal | 給与 |
Commission | decimal? | 歩合(null あり) |
ManagerId | int? | 上司の社員番号(null あり) |
DepartmentId | int | 部署番号 |
- 引数付きコンストラクター(7 つの引数)を作成
PrintProfileメソッドを作成し、次の形式で表示する:- 歩合や上司番号が
nullの場合は"未設定"と表示する
- 歩合や上司番号が
Main メソッドの処理
- 次の社員データで
Employeeを作成し、PrintProfileで表示する
new Employee( 1001, "山田二郎", new DateTime(2001, 4, 1), 500000m, null, // 歩合なし null, // 上司なし(社長) 1)実行結果例:
社員番号:1001氏名:山田二郎入社日:2001/04/01給与:500000円歩合:未設定上司ID:未設定部署ID:1条件:
decimal?とint?を使う??またはif文で null を扱う- 日付は
yyyy/MM/dd形式で表示する
提出前チェックリスト
Section titled “提出前チェックリスト”- プログラムを Visual Studio から実行できる
- 値型の代入と参照型の代入の違いを確認できている
-
structで構造体を作成できている(発展) -
enumで列挙型を作成できている -
switch文で列挙型を判定できている -
nullを返すメソッドとnullチェックを書けている -
int?、decimal?、DateTime?を使えている(発展) -
HasValue・Valueを使えている(発展) -
??を使えている(発展) - クラス・構造体・列挙型を別ファイルに書けている
- インデントが整っている
- 課題を提出した(Git の
commit→push、または講師指示があればKadai11フォルダをサーバへコピーし、提出先フォルダに提出メモ.txtを作成)
Git への提出
Section titled “Git への提出”タイマーが鳴ったら、第 11 章の課題プロジェクト群(Kadai11 ソリューション)をその場で Git に提出します。
git statusgit add .git commit -m "Chapter11 タイマー提出: <どこまで完成> / <詰まったポイント>"git push実行例:
git commit -m "Chapter11 タイマー提出: 11-1〜11-2完成、11-3は途中 / null 返却の書き方で詰まった"Git の詳しい操作は 付録 C、コミットメッセージ形式とサーバコピー提出は本章冒頭「演習課題 > 提出ルール」を参照してください。
この章のまとめ
Section titled “この章のまとめ”この章では、値型と参照型を学習しました。
- 構造体(
struct)は値型、クラス(class)は参照型 int・bool・DateTimeなどは構造体(=値型)- 列挙型(
enum)は決まった選択肢を安全に扱うための型 - 値型は代入時に値そのものがコピーされる
- 参照型は代入時に参照先(場所)がコピーされ、複数の変数が同じオブジェクトを指すことがある
- 参照型の
newは別のオブジェクトを作る - メソッドに渡したときも、値型は値、参照型は参照(場所)がコピーされる
nullは「参照先がない」状態を表すnullの変数を使うとNullReferenceExceptionになる- 値型で null を扱いたいときは
int?・decimal?などの null 許容値型を使う HasValue・Value・??で null 許容値型を扱える- DB の NULL は C# の null と対応し、
int?・decimal?などで受け取る
次章では、List<T> と LINQ を学習します。
配列より柔軟に複数のデータを扱える List<T> を使い、さらに LINQ による検索・絞り込み・並び替えを学びます。
後の DB 接続編では、複数件の社員情報を List<Employee> として扱うため、この章と次章の内容が土台になります。