C# nullableとは?値型・参照型の違いから警告対策まで初心者向けに徹底解説
はじめに
C#で開発していると、nullやnullableという言葉をよく見かけます。
特に、次のようなコードで警告が出て戸惑った経験はないでしょうか。
C#string name = null;
Console.WriteLine(name.Length);
C#では、nullを正しく扱わないと、実行時にNullReferenceExceptionというエラーが発生することがあります。これは初心者だけでなく、経験者でもよく遭遇する代表的なエラーです。
そこで重要になるのが、C# nullableです。
nullableを理解すると、「この値はnullになる可能性があるのか」「nullでないことを保証できるのか」をコード上で明確に表せるようになります。
この記事では、C# nullableについて、値型と参照型の違い、int?やstring?の使い方、nullable警告の対策、実践的な設計の考え方まで初心者向けにわかりやすく解説します。
1. C# nullableとは?nullを安全に扱うための基本
C# nullableとは、簡単にいうとnullを扱うための仕組みです。
C#では、変数に「値がある状態」だけでなく、「値がない状態」を表したい場面があります。その「値がない状態」を表す代表的な値がnullです。
ただし、nullは便利な一方で、扱い方を間違えるとエラーの原因になります。nullableは、nullを安全に扱うために用意された重要な機能です。
1-1. nullableの意味:nullを許可する型・値として扱う仕組み
nullableは直訳すると「nullにできる」「nullを許容する」という意味です。
C#では、型に?を付けることで、その値がnullになる可能性があることを表します。
C#int? age = null;
string? name = null;
int?は「nullを許可するint」、string?は「nullを許可するstring」という意味です。
つまりnullableを使うと、コードを読む人やコンパイラに対して、次のような意図を伝えられます。
C#string name; // nullにしない想定
string? memo; // nullになる可能性がある
このように、nullableは単なる記法ではなく、nullを許可するかどうかを設計として表すための仕組みです。
1-2. nullとは何か?初心者がつまずきやすいポイント
nullとは、「何も参照していない」「値が存在しない」ことを表す特殊な値です。
たとえば、次のような変数があるとします。
C#string? name = null;
この場合、nameには文字列が入っていません。空文字""とも違います。
C#string empty = "";
string? none = null;
emptyは「長さ0の文字列」です。一方で、noneは「文字列そのものが存在しない状態」です。
この違いは重要です。
C#Console.WriteLine(empty.Length); // 0
Console.WriteLine(none.Length); // エラーになる可能性
nullの変数に対して.Lengthや.ToString()などを呼び出すと、実行時エラーが発生します。
1-3. nullableが必要になる理由:NullReferenceExceptionを防ぐため
C#でnullを扱うときに最も注意したいのが、NullReferenceExceptionです。
これは、nullの可能性がある変数に対して、メンバーやメソッドを呼び出したときに発生します。
C#string? name = null;
Console.WriteLine(name.Length); // NullReferenceExceptionの可能性
nameがnullの場合、文字列オブジェクトが存在しないため、Lengthを取得できません。
nullableを有効にしていると、C#コンパイラは次のように警告してくれます。
C#string? name = null;
Console.WriteLine(name.Length); // 警告: nullの可能性がある
つまりnullableは、実行前に「ここはnullで落ちるかもしれない」と教えてくれる仕組みです。
1-4. C#のnullableには「値型」と「参照型」の2種類がある
C# nullableを理解するうえで重要なのが、値型のnullableと参照型のnullableの違いです。
C#の型は大きく分けると、値型と参照型があります。
値型の例は次のとおりです。
C#int
double
bool
DateTime
参照型の例は次のとおりです。
C#string
object
配列
class
値型は通常nullを代入できません。
C#int age = null; // エラー
しかし、?を付けるとnullを代入できます。
C#int? age = null; // OK
一方、参照型はもともとnullを代入できます。
C#string name = null;
ただし、C# 8.0以降ではnullable参照型を有効にすることで、stringとstring?を区別できるようになりました。
C#string name = "Alice"; // null不可の意図
string? memo = null; // null許可の意図
この違いが、C# nullableを理解する大きなポイントです。
1-5. まず覚えるべき記法:?・!・??・?.
nullable関連でよく使う記法には、次のものがあります。
C#T? // nullを許可する
! // nullではないとコンパイラに伝える
?? // nullの場合の代替値を指定する
?. // nullでない場合だけメンバーにアクセスする
例を見てみましょう。
C#string? name = null;
string displayName = name ?? "未設定";
Console.WriteLine(displayName);
??は、左辺がnullなら右辺を使う演算子です。
C#string? name = null;
int? length = name?.Length;
?.は、nameがnullでない場合だけLengthにアクセスします。nullの場合は例外を出さず、結果もnullになります。
C#string? name = GetName();
Console.WriteLine(name!.Length);
!は「この値はnullではない」とコンパイラに伝える演算子です。ただし、実際にnullだった場合は実行時エラーになるため、使いすぎには注意が必要です。
2. 値型のnullable:int?・DateTime?・bool?の使い方
値型のnullableは、C# nullableの中でも昔から使われている機能です。
int?、DateTime?、bool?などの形で使い、値型にnullを代入できるようにします。
2-1. 値型は通常nullを代入できない
値型は、基本的に必ず値を持ちます。
C#int count = 0;
bool isActive = false;
DateTime createdAt = DateTime.Now;
そのため、通常はnullを代入できません。
C#int age = null; // エラー
DateTime date = null; // エラー
bool flag = null; // エラー
しかし、実際の開発では「数値が未入力」「日付が未設定」「true/falseのどちらでもない」といった状態を表したいことがあります。
そのような場合に、値型nullableを使います。
C#int? age = null;
DateTime? birthday = null;
bool? agreed = null;
2-2. Nullable<T>とT?の違い
値型nullableには、正式な書き方と省略記法があります。
C#Nullable<int> age1 = null;
int? age2 = null;
この2つは基本的に同じ意味です。
int?は、Nullable<int>の省略形です。
C#int? age;
は、実質的に次のような意味になります。
C#Nullable<int> age;
通常のコードでは、読みやすいint?の形がよく使われます。
C#DateTime? publishedAt = null;
decimal? price = null;
bool? isConfirmed = null;
2-3. int?に値を入れる・nullを入れる基本例
int?には、通常のintの値もnullも入れられます。
C#int? score = 100;
Console.WriteLine(score); // 100
score = null;
Console.WriteLine(score); // 何も表示されない
値があるかどうかを確認するには、後述するHasValueを使うか、nullチェックを行います。
C#int? score = 80;
if (score != null)
{
Console.WriteLine($"点数は{score}です");
}
else
{
Console.WriteLine("点数は未設定です");
}
nullable値型は、データベースやフォーム入力と相性がよく、実務でも頻繁に登場します。
2-4. HasValueとValueの使い方
値型nullableには、HasValueとValueというプロパティがあります。
C#int? age = 20;
if (age.HasValue)
{
Console.WriteLine(age.Value);
}
HasValueは、値が入っているかどうかをtrueまたはfalseで返します。
C#int? age = null;
Console.WriteLine(age.HasValue); // false
Valueは、nullableの中に入っている実際の値を取り出します。
C#int? age = 30;
int actualAge = age.Value;
ただし、値がnullの状態でValueを使うと例外が発生します。
C#int? age = null;
Console.WriteLine(age.Value); // 例外
そのため、Valueを使う場合は必ず事前にHasValueで確認しましょう。
C#if (age.HasValue)
{
Console.WriteLine(age.Value);
}
2-5. GetValueOrDefault()で既定値を取得する方法
nullable値型には、GetValueOrDefault()という便利なメソッドがあります。
これは、値がある場合はその値を返し、nullの場合は型の既定値を返します。
C#int? count = null;
int result = count.GetValueOrDefault();
Console.WriteLine(result); // 0
intの既定値は0です。
boolの場合はfalse、DateTimeの場合はDateTimeの既定値になります。
C#bool? isActive = null;
Console.WriteLine(isActive.GetValueOrDefault()); // false
任意の既定値を指定することもできます。
C#int? count = null;
int result = count.GetValueOrDefault(10);
Console.WriteLine(result); // 10
ただし、実務では??を使うことも多いです。
C#int? count = null;
int result = count ?? 10;
どちらもnullの場合の代替値を設定する方法としてよく使われます。
2-6. 値型nullableがよく使われる場面:DB・フォーム・未入力データ
値型nullableは、次のような場面でよく使われます。
データベースのNULLを扱う場合です。
C#DateTime? deletedAt = null;
削除日時が入っていれば削除済み、nullなら未削除、という設計はよくあります。
フォーム入力でも使われます。
C#int? age = null;
年齢入力欄が未入力の場合、0とするよりもnullの方が自然な場合があります。
検索条件でもnullableは便利です。
C#int? minPrice = null;
int? maxPrice = 5000;
この場合、最低価格は指定なし、最高価格は5000円まで、という意味を表せます。
0とnullは意味が違います。0は値としての0、nullは未設定です。この違いを明確にしたいときに、値型nullableが役立ちます。
3. 参照型のnullable:string?とstringの違い
C# nullableで初心者が混乱しやすいのが、string?とstringの違いです。
値型の場合、intにはnullを代入できず、int?にはnullを代入できるため違いがわかりやすいです。
一方、参照型であるstringは、昔からnullを代入できました。
C#string name = null;
では、なぜstring?が必要なのでしょうか。
3-1. 参照型はもともとnullを持てるのに、なぜstring?が必要なのか
C# 8.0より前は、参照型には普通にnullを代入できました。
C#string name = null;
しかし、この書き方では、nameがnullになる想定なのか、本来はnullにしたくないのに誤ってnullを入れているのかがわかりません。
その結果、次のようなコードでもコンパイルは通ってしまいます。
C#string name = null;
Console.WriteLine(name.Length);
実行すると、NullReferenceExceptionが発生する可能性があります。
そこでC# 8.0以降では、nullable参照型という機能が追加されました。
C#string name = "Alice"; // nullにしない
string? memo = null; // nullを許可する
このように、参照型でもnullを許可するかどうかを明示できるようになりました。
3-2. nullable参照型はC# 8.0以降のコンパイラ機能
nullable参照型は、C# 8.0以降で導入された機能です。
ただし、値型nullableとは少し性質が違います。
値型nullableのint?は、実際にNullable<int>という型になります。
C#int? age = null;
一方、参照型nullableのstring?は、実行時にまったく別の型になるわけではありません。
C#string? name = null;
string?は、主にコンパイラが静的解析を行うための情報です。
つまり、string?は「この変数はnullになる可能性がある」とコンパイラに伝えるための注釈のようなものです。
3-3. stringはnull不可、string?はnull許可という意図を表す
nullable参照型を有効にすると、stringとstring?は次のような意味になります。
C#string name; // nullを想定しない
string? memo; // nullを想定する
たとえば、ユーザー名は必ず必要だが、備考は未入力でもよい場合は次のように書けます。
C#public class User
{
public string Name { get; set; } = "";
public string? Memo { get; set; }
}
Nameは必須項目なのでstring、Memoは任意項目なのでstring?にしています。
このように、?を付けるかどうかで、設計上の意図をコードに表現できます。
3-4. nullable参照型は実行時チェックではなく静的解析である
nullable参照型は、実行時に自動でnullチェックしてくれる機能ではありません。
たとえば、次のコードを考えます。
C#string? name = null;
Console.WriteLine(name.Length);
nullable参照型が有効であれば、コンパイラは警告を出します。
しかし、警告を無視して実行すれば、NullReferenceExceptionが発生する可能性があります。
つまり、nullable参照型は「実行時に守ってくれる機能」ではなく、「コンパイル時に危険な箇所を教えてくれる機能」です。
そのため、警告が出たら無視するのではなく、nullチェックや設計の見直しを行うことが大切です。
3-5. string?を使うべきケース・使わない方がよいケース
string?を使うべきなのは、実際にnullになる可能性がある場合です。
たとえば、次のようなケースです。
C#public string? MiddleName { get; set; }
public string? PhoneNumber { get; set; }
public string? DeletedReason { get; set; }
ミドルネーム、電話番号、削除理由などは、存在しない場合があります。このような項目はstring?が自然です。
一方で、必ず値が必要な項目にはstring?を付けない方がよいです。
C#public string UserName { get; set; } = "";
public string Email { get; set; } = "";
何でもstring?にしてしまうと、コード全体でnullチェックが増え、逆に扱いづらくなります。
nullableの基本は、nullを許可する理由がある場合だけ?を付けることです。
4. nullableを有効化する方法
C#でnullable参照型を使うには、nullableの設定を有効にする必要があります。
値型nullableのint?などは以前から使えますが、string?などのnullable参照型による警告を有効にするには設定が必要です。
4-1. プロジェクト全体で有効化する:.csprojの<Nullable>enable</Nullable>
プロジェクト全体でnullableを有効にするには、.csprojファイルに次の設定を追加します。
XML<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
たとえば、次のような形です。
XML<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
この設定を入れると、プロジェクト全体でnullable参照型の警告が有効になります。
C#string name = null; // 警告
string? memo = null; // OK
新しい.NETプロジェクトでは、最初から<Nullable>enable</Nullable>が設定されていることも多いです。
4-2. ファイル単位で有効化する:#nullable enable
プロジェクト全体ではなく、特定のファイルだけnullableを有効にしたい場合は、ファイルの先頭に次のように書きます。
C##nullable enable
public class User
{
public string Name { get; set; } = "";
public string? Memo { get; set; }
}
逆に、特定のファイルだけ無効にすることもできます。
C##nullable disable
既存プロジェクトでいきなり全体を有効にすると警告が大量に出る場合があります。そのような場合は、まず一部のファイルだけ#nullable enableで有効化する方法も有効です。
4-3. nullableの設定モード:enable・disable・warnings・annotations
nullableには、主に次の設定モードがあります。
XML<Nullable>enable</Nullable>
<Nullable>disable</Nullable>
<Nullable>warnings</Nullable>
<Nullable>annotations</Nullable>
enableは、nullable注釈と警告の両方を有効にします。通常はこの設定を使います。
XML<Nullable>enable</Nullable>
disableは、nullable機能を無効にします。
XML<Nullable>disable</Nullable>
warningsは、nullable警告のみを有効にします。
XML<Nullable>warnings</Nullable>
annotationsは、?などのnullable注釈のみを有効にします。
XML<Nullable>annotations</Nullable>
初心者の場合は、細かく使い分けるよりも、まずはenableを覚えておけば十分です。
4-4. 既存プロジェクトでnullableを有効にすると警告が増える理由
既存プロジェクトでnullableを有効にすると、大量の警告が出ることがあります。
理由は、これまでnullの可能性を明示していなかったコードに対して、コンパイラが一気に解析を行うからです。
たとえば、次のようなコードです。
C#public class User
{
public string Name { get; set; }
}
nullableを有効にすると、Nameがコンストラクター終了時点で初期化されていないため、警告が出ます。
C#public class User
{
public string Name { get; set; } = "";
}
または、コンストラクターで初期化します。
C#public class User
{
public string Name { get; }
public User(string name)
{
Name = name;
}
}
既存コードでは、nullになる可能性を暗黙的に扱っている箇所が多いため、nullableを有効にすると警告が増えやすいのです。
4-5. 初心者におすすめの導入手順
初心者がnullableを導入する場合は、次のような順番がおすすめです。
まず、新規プロジェクトでは最初からnullableを有効にします。
XML<Nullable>enable</Nullable>
次に、警告が出たらすぐに原因を確認します。
C#string? name = GetName();
Console.WriteLine(name.Length); // 警告
この場合は、nullチェックを追加します。
C#if (name != null)
{
Console.WriteLine(name.Length);
}
既存プロジェクトでは、いきなり全体を有効にするよりも、重要なクラスや新しく修正するファイルから少しずつ対応するのが現実的です。
大切なのは、警告を一気に消すことではなく、nullの可能性を理解しながら安全なコードにしていくことです。
5. nullable関連の警告と対策
nullableを有効にすると、C#コンパイラはnullの可能性があるコードに対して警告を出します。
代表的な警告には、CS8600、CS8602、CS8603、CS8618などがあります。
これらは初心者には難しく見えますが、意味を理解すれば対処しやすくなります。
5-1. nullable警告とは?エラーではなく「nullの可能性」を知らせるサイン
nullable警告は、基本的にはエラーではありません。
そのため、警告が出ていてもコンパイルできる場合があります。
しかし、警告を無視すると、実行時にNullReferenceExceptionが発生する可能性があります。
たとえば、次のコードです。
C#string? name = null;
Console.WriteLine(name.Length);
このコードは、nullable警告が出ます。
コンパイラは、「nameはnullかもしれないのにLengthを呼び出している」と教えてくれています。
nullable警告は、バグを未然に防ぐためのサインです。警告が出たら、単に消すのではなく、nullになる可能性が本当にあるのかを確認しましょう。
5-2. CS8600:nullの可能性がある値を代入している
CS8600は、nullの可能性がある値を、nullを許可していない変数に代入しているときに出る警告です。
C#string? nullableName = GetName();
string name = nullableName; // CS8600の可能性
nullableNameはstring?なのでnullの可能性があります。しかし、nameはstringなのでnullを想定していません。
対策として、nullの場合の値を指定します。
C#string? nullableName = GetName();
string name = nullableName ?? "未設定";
または、nullでないことを確認してから代入します。
C#string? nullableName = GetName();
if (nullableName != null)
{
string name = nullableName;
Console.WriteLine(name);
}
5-3. CS8602:nullの可能性がある参照を使用している
CS8602は、nullの可能性がある変数に対してメンバーアクセスしているときに出る警告です。
C#string? name = GetName();
Console.WriteLine(name.Length); // CS8602の可能性
nameがnullの場合、Lengthにアクセスできません。
対策は、nullチェックです。
C#if (name != null)
{
Console.WriteLine(name.Length);
}
または、null条件演算子?.を使います。
C#Console.WriteLine(name?.Length);
nullの場合に既定値を使いたいなら、??と組み合わせます。
C#Console.WriteLine(name?.Length ?? 0);
5-4. CS8603:nullの可能性がある値を返している
CS8603は、nullを返す可能性があるのに、戻り値の型がnullを許可していない場合に出る警告です。
C#string FindName(int id)
{
if (id == 0)
{
return null; // CS8603の可能性
}
return "Alice";
}
このメソッドの戻り値はstringなので、nullを返すべきではありません。
nullを返す可能性があるなら、戻り値をstring?にします。
C#string? FindName(int id)
{
if (id == 0)
{
return null;
}
return "Alice";
}
または、nullではなく既定値を返します。
C#string FindName(int id)
{
if (id == 0)
{
return "";
}
return "Alice";
}
どちらがよいかは設計次第です。「見つからない」ことをnullで表すのか、空文字で表すのかを明確にしましょう。
5-5. CS8618:コンストラクター終了時に非nullableプロパティが初期化されていない
CS8618は、非nullableのプロパティが初期化されていない場合に出る警告です。
C#public class User
{
public string Name { get; set; } // CS8618の可能性
}
Nameはstringなのでnull不可の意図ですが、初期値が設定されていません。
対策として、初期値を設定します。
C#public class User
{
public string Name { get; set; } = "";
}
または、コンストラクターで初期化します。
C#public class User
{
public string Name { get; }
public User(string name)
{
Name = name;
}
}
C# 11以降では、requiredを使う方法もあります。
C#public class User
{
public required string Name { get; set; }
}
この場合、オブジェクト作成時にNameの設定が必要になります。
C#var user = new User
{
Name = "Alice"
};
5-6. 警告を消すのではなく、nullの設計を見直す考え方
nullable警告が出たときに、すぐ!を付けて警告だけ消すのはおすすめできません。
C#Console.WriteLine(name!.Length);
この書き方は、コンパイラには「nullではない」と伝えますが、実際にnullだった場合は実行時エラーになります。
大切なのは、次のように考えることです。
この値は本当にnullになる可能性があるのか。
nullになるなら、どこでチェックするのか。
nullの場合、どう処理するのか。
nullを許可しないなら、どこで必ず初期化するのか。
nullable警告は、設計を見直す良いきっかけになります。単に警告を消すのではなく、nullの扱いをコード上で明確にしましょう。
6. nullable警告を安全に解消する実践パターン
nullable警告は、いくつかの定番パターンを覚えると安全に解消できます。
ここでは、実務でよく使う対策を紹介します。
6-1. nullチェックで安全に分岐する
最も基本的な対策は、nullチェックです。
C#string? name = GetName();
if (name != null)
{
Console.WriteLine(name.Length);
}
else
{
Console.WriteLine("名前がありません");
}
if (name != null)の中では、コンパイラはnameがnullではないと判断します。
そのため、次のようなメンバーアクセスも安全です。
C#if (name != null)
{
Console.WriteLine(name.ToUpper());
}
初心者はまず、このnullチェックをしっかり使えるようにしましょう。
6-2. ??でデフォルト値を設定する
??は、左辺がnullの場合に右辺の値を使う演算子です。
C#string? name = GetName();
string displayName = name ?? "ゲスト";
nameがnullでなければその値を使い、nullなら"ゲスト"を使います。
数値でもよく使います。
C#int? count = GetCount();
int actualCount = count ?? 0;
??を使うと、nullの場合の代替値を簡潔に書けます。
C#Console.WriteLine(name ?? "未設定");
ただし、何でも既定値に置き換えればよいわけではありません。nullと空文字、nullと0の意味が違う場合は、安易に置き換えないようにしましょう。
6-3. ?.でnullの可能性があるオブジェクトに安全にアクセスする
?.は、null条件演算子です。
対象がnullでない場合だけ、メンバーにアクセスします。
C#string? name = GetName();
int? length = name?.Length;
nameがnullなら、name?.Lengthの結果もnullになります。
?.は、オブジェクトのプロパティをたどるときにも便利です。
C#string? city = user?.Address?.City;
このコードでは、userまたはAddressがnullの場合でも、例外を出さずにcityはnullになります。
nullの場合に既定値を使いたい場合は、??と組み合わせます。
C#string city = user?.Address?.City ?? "不明";
6-4. is not nullでnullでないことを明示する
C#では、is not nullを使ってnullでないことを明示できます。
C#string? name = GetName();
if (name is not null)
{
Console.WriteLine(name.Length);
}
name != nullと似ていますが、パターンマッチングの文脈で使いやすく、読みやすい場合があります。
また、次のように変数を同時に扱うコードでも便利です。
C#object? value = GetValue();
if (value is string text)
{
Console.WriteLine(text.Length);
}
この場合、valueがstringであり、nullではない場合だけtextとして扱えます。
6-5. requiredやコンストラクターで初期化漏れを防ぐ
非nullableプロパティは、初期化漏れがあると警告になります。
C#public class Product
{
public string Name { get; set; }
}
対策として、コンストラクターで初期化します。
C#public class Product
{
public string Name { get; }
public Product(string name)
{
Name = name;
}
}
または、初期値を設定します。
C#public class Product
{
public string Name { get; set; } = "";
}
C# 11以降では、requiredを使って、オブジェクト作成時の設定を強制できます。
C#public class Product
{
public required string Name { get; set; }
}
使用例です。
C#var product = new Product
{
Name = "ノートPC"
};
必須項目はnullableにするのではなく、必ず初期化される設計にすることが重要です。
6-6. null許容演算子!を使う場合の注意点
!は、null許容演算子、またはnull免除演算子と呼ばれます。
C#string? name = GetName();
Console.WriteLine(name!.Length);
これは、コンパイラに対して「nameはnullではないとみなしてよい」と伝える記法です。
ただし、実際にnullチェックを行うわけではありません。
C#string? name = null;
Console.WriteLine(name!.Length); // 実行時エラー
そのため、!は慎重に使う必要があります。
使ってよい場面の例は、フレームワークやライブラリの仕様上、コンパイラにはわからないが実際には必ず値が設定される場合です。
C#public string Name { get; set; } = null!;
このような書き方は、ORMやシリアライザーなどが後から値を設定する場合に使われることがあります。
ただし、初心者のうちは、警告を消すためだけに!を使うのは避けた方がよいです。まずはnullチェック、??、コンストラクター初期化で対応できないかを検討しましょう。
7. nullableを使ったコード例で理解する
ここからは、C# nullableの使い方を具体的なコード例で確認します。
値型nullable、参照型nullable、メソッド引数、戻り値、クラスプロパティなど、実務でよく使う形を見ていきましょう。
7-1. 値型nullableの基本コード例
まずはint?の例です。
C#int? age = null;
if (age.HasValue)
{
Console.WriteLine($"年齢は{age.Value}歳です");
}
else
{
Console.WriteLine("年齢は未入力です");
}
ageに値がある場合だけValueを使っています。
??を使うと、もっと簡潔に書けます。
C#int? age = null;
int displayAge = age ?? 0;
Console.WriteLine(displayAge);
DateTime?の例です。
C#DateTime? publishedAt = null;
if (publishedAt is not null)
{
Console.WriteLine($"公開日: {publishedAt.Value:yyyy/MM/dd}");
}
else
{
Console.WriteLine("未公開です");
}
日付が未設定の場合にnullを使うのは、実務でもよくあるパターンです。
7-2. 参照型nullableの基本コード例
次に、string?の例です。
C#string? nickname = GetNickname();
if (nickname is not null)
{
Console.WriteLine(nickname.Length);
}
else
{
Console.WriteLine("ニックネームは未設定です");
}
nicknameはnullの可能性があるため、使う前にチェックしています。
??を使う例です。
C#string? nickname = GetNickname();
string displayName = nickname ?? "名無し";
Console.WriteLine(displayName);
?.を使う例です。
C#string? nickname = GetNickname();
int length = nickname?.Length ?? 0;
Console.WriteLine(length);
このように、string?を使う場合は、nullの場合の処理をセットで考えることが大切です。
7-3. メソッドの引数にnullableを使う例
メソッドの引数にnullableを使うと、「nullを渡してよいかどうか」を明確にできます。
C#void PrintMessage(string message)
{
Console.WriteLine(message);
}
このメソッドは、messageにnullを渡さない前提です。
一方、nullを許可するならstring?にします。
C#void PrintMessage(string? message)
{
Console.WriteLine(message ?? "メッセージはありません");
}
呼び出し側は、nullを渡せます。
C#PrintMessage(null);
値型でも同じです。
C#void PrintAge(int? age)
{
if (age is null)
{
Console.WriteLine("年齢は未入力です");
}
else
{
Console.WriteLine($"{age}歳です");
}
}
引数にnullableを使う場合は、メソッド内でnullの場合の処理を必ず考えましょう。
7-4. メソッドの戻り値にnullableを使う例
戻り値にnullableを使うと、「結果が存在しない可能性がある」ことを表せます。
C#string? FindUserName(int id)
{
if (id == 1)
{
return "Alice";
}
return null;
}
呼び出し側は、戻り値がnullかもしれない前提で扱います。
C#string? userName = FindUserName(2);
if (userName is not null)
{
Console.WriteLine(userName);
}
else
{
Console.WriteLine("ユーザーが見つかりません");
}
??を使うこともできます。
C#string displayName = FindUserName(2) ?? "不明なユーザー";
「見つからない」という状態をnullで表す場合、戻り値をstring?にすることで、呼び出し側にnullチェックを促せます。
7-5. クラスのプロパティでnullableを使う例
クラスのプロパティでは、必須項目と任意項目をnullableで分けるとわかりやすくなります。
C#public class User
{
public required string Name { get; set; }
public string? Nickname { get; set; }
public DateTime? Birthday { get; set; }
}
この例では、Nameは必須、NicknameとBirthdayは任意です。
使う側は次のようになります。
C#var user = new User
{
Name = "Alice",
Nickname = null,
Birthday = null
};
表示するときは、nullを考慮します。
C#Console.WriteLine(user.Name);
Console.WriteLine(user.Nickname ?? "ニックネームなし");
Console.WriteLine(user.Birthday?.ToString("yyyy/MM/dd") ?? "誕生日未設定");
nullableを適切に使うことで、プロパティの意味が明確になります。
7-6. nullチェック前後で警告がどう変わるか
nullable警告は、nullチェックを行うことで解消されます。
警告が出る例です。
C#string? name = GetName();
Console.WriteLine(name.Length);
nameはnullの可能性があるため、Lengthにアクセスすると警告が出ます。
nullチェック後は安全です。
C#string? name = GetName();
if (name is not null)
{
Console.WriteLine(name.Length);
}
ifの中では、コンパイラがnameはnullではないと判断します。
??で代替値を使う場合も警告を避けられます。
C#string? name = GetName();
string safeName = name ?? "未設定";
Console.WriteLine(safeName.Length);
このように、nullable警告は単なる邪魔なメッセージではなく、安全なコードを書くためのヒントです。
8. nullable設計のベストプラクティス
C# nullableを正しく使うには、文法だけでなく設計の考え方も重要です。
ここでは、nullableを使うときに意識したいベストプラクティスを紹介します。
8-1. nullを許可する理由がある場合だけ?を付ける
nullableを使う基本方針は、nullを許可する理由がある場合だけ?を付けることです。
たとえば、必ず存在するユーザー名には?を付ける必要はありません。
C#public string UserName { get; set; } = "";
一方、任意入力の備考欄はnullを許可してもよいでしょう。
C#public string? Note { get; set; }
何でも?を付けると、nullチェックが増えてコードが複雑になります。
C#public string? UserName { get; set; }
public string? Email { get; set; }
public string? Password { get; set; }
本当にnullになる可能性があるのかを考え、不要なnullableは避けましょう。
8-2. nullではなく空文字・空配列・既定値で表せないか検討する
nullを使う前に、空文字、空配列、既定値で表せないかを考えることも重要です。
たとえば、タグ一覧がない場合、nullではなく空配列で表す方が扱いやすいことがあります。
C#public List<string> Tags { get; set; } = new();
この設計なら、呼び出し側はnullチェックせずにループできます。
C#foreach (var tag in article.Tags)
{
Console.WriteLine(tag);
}
一方、次のようにnullableにすると、毎回nullチェックが必要になります。
C#public List<string>? Tags { get; set; }
空文字も同様です。
C#public string Description { get; set; } = "";
「未入力」を空文字で表せるなら、string?にしない方がシンプルです。
ただし、空文字とnullで意味が違う場合は、nullableを使う価値があります。重要なのは、意味を明確にすることです。
8-3. API・DB・外部入力ではnullableを前提に設計する
API、データベース、フォーム、外部ファイルなどから取得する値は、想定外のnullが入る可能性があります。
そのため、外部入力を扱う部分ではnullableを前提に設計することが大切です。
C#public class UserRequest
{
public string? Name { get; set; }
public string? Email { get; set; }
}
外部から受け取った値は、検証してから内部の安全な型に変換します。
C#if (string.IsNullOrWhiteSpace(request.Name))
{
throw new ArgumentException("名前は必須です");
}
var user = new User
{
Name = request.Name
};
このように、外部入力ではnullを疑い、内部処理ではnullを減らす設計にすると、安全で扱いやすいコードになります。
8-4. 非nullableプロパティは初期化方法を明確にする
非nullableプロパティを使う場合は、必ず初期化方法を明確にしましょう。
初期値を設定する方法です。
C#public class User
{
public string Name { get; set; } = "";
}
コンストラクターで設定する方法です。
C#public class User
{
public string Name { get; }
public User(string name)
{
Name = name;
}
}
requiredを使う方法です。
C#public class User
{
public required string Name { get; set; }
}
非nullableなのに初期化されていない状態は、設計として矛盾しています。
C#public class User
{
public string Name { get; set; } // 警告
}
「この値は必ず存在する」と決めたなら、どこで必ず設定されるのかもセットで決めましょう。
8-5. !で警告を無理に消さない
!は便利ですが、使いすぎるとnullableの意味がなくなります。
C#Console.WriteLine(user!.Name!.Length);
このようなコードは、警告は消えるかもしれませんが、安全になったわけではありません。
本来は、nullチェックや初期化で対応すべきです。
C#if (user is not null && user.Name is not null)
{
Console.WriteLine(user.Name.Length);
}
または、設計を見直してNameを非nullableにします。
C#public class User
{
public required string Name { get; set; }
}
!は最後の手段です。コンパイラにはわからないが、人間にはnullでないと保証できる場合だけ使いましょう。
8-6. チーム開発でnullableルールを統一する
チーム開発では、nullableの使い方を統一することが重要です。
人によってstring?を多用したり、警告を!で消したりすると、コードの品質がばらつきます。
たとえば、次のようなルールを決めておくとよいでしょう。
必須プロパティは非nullableにする。
任意プロパティだけnullableにする。
外部入力DTOはnullableを許可する。
内部モデルではできるだけnullを減らす。
!は原則使わない。
nullable警告は放置しない。
このようにルールを統一すると、コードレビューもしやすくなり、NullReferenceExceptionの発生も減らせます。
9. nullableでよくある疑問
最後に、C# nullableで初心者がよく疑問に思うポイントを整理します。
9-1. int?とintの違いは何か
intは通常の整数型で、nullを代入できません。
C#int age = 20;
int?は、nullを許可する整数型です。
C#int? age = null;
intは必ず数値を持ちますが、int?は数値またはnullを持ちます。
C#int value1 = 0; // 0という値
int? value2 = null; // 値がない
0とnullは意味が違います。0は数値としての0、nullは未設定や不明を表します。
9-2. string?とstringの違いは何か
nullable参照型が有効な場合、stringはnullを想定しない文字列、string?はnullを許可する文字列です。
C#string name = "Alice";
string? memo = null;
stringは必ず値がある前提なので、nullを代入すると警告が出ます。
C#string name = null; // 警告
string?はnullになる可能性があるため、使うときにはnullチェックが必要です。
C#string? memo = null;
Console.WriteLine(memo?.Length ?? 0);
9-3. ?と!の違いは何か
?は、nullを許可することを表します。
C#string? name = null;
int? age = null;
一方、!は、コンパイラに「これはnullではない」と伝えるための記法です。
C#Console.WriteLine(name!.Length);
?はnullの可能性を表す記号、!はnull警告を抑制する記号です。
ただし、!は実行時にnullチェックするわけではありません。
C#string? name = null;
Console.WriteLine(name!.Length); // 実行時エラー
基本的には、?でnullの可能性を正しく表し、!はどうしても必要な場合だけ使いましょう。
9-4. nullableを有効にすると既存コードは壊れるのか
nullableを有効にしても、多くの場合、既存コードがすぐに動かなくなるわけではありません。
nullable警告は基本的に警告であり、エラーではないためです。
ただし、警告が大量に出ることがあります。
C#string name = null;
Console.WriteLine(name.Length);
このようなコードは、nullableを有効にすると警告の対象になります。
既存プロジェクトでは、いきなり全警告を直そうとすると大変です。まずは新しく書くコードからnullableを意識し、重要な部分から少しずつ対応するのがよいでしょう。
9-5. nullable警告は無視してもよいのか
nullable警告は無視しない方がよいです。
警告は、将来的にNullReferenceExceptionにつながる可能性を示しています。
たとえば、次の警告を無視すると危険です。
C#string? name = GetName();
Console.WriteLine(name.Length);
nameがnullなら実行時にエラーになります。
もちろん、すべての警告がすぐにバグになるとは限りません。しかし、警告を放置すると、本当に危険な箇所が見えにくくなります。
nullable警告は、できるだけ解消する方針にするのがおすすめです。
9-6. nullableとNullReferenceExceptionの関係は何か
nullableは、NullReferenceExceptionを防ぐための重要な仕組みです。
NullReferenceExceptionは、nullの変数に対してメンバーやメソッドを呼び出したときに発生します。
C#string? name = null;
Console.WriteLine(name.Length); // NullReferenceExceptionの可能性
nullableを有効にすると、コンパイラがこのような危険なコードに警告を出してくれます。
C#if (name is not null)
{
Console.WriteLine(name.Length);
}
nullableを使えば、NullReferenceExceptionを完全にゼロにできるわけではありません。しかし、発生しやすい箇所を事前に見つけやすくなります。
つまりnullableは、nullによるバグを減らすための強力な補助機能です。
まとめ
C# nullableは、nullを安全に扱うための重要な仕組みです。
値型nullableでは、int?、DateTime?、bool?のように、通常nullを持てない値型にnullを許可できます。
C#int? age = null;
DateTime? publishedAt = null;
bool? agreed = null;
参照型nullableでは、stringとstring?を使い分けることで、nullを許可するかどうかをコード上で明確にできます。
C#string name = "Alice";
string? memo = null;
nullableを有効にすると、CS8600、CS8602、CS8603、CS8618などの警告が出ることがあります。これらはエラーではありませんが、nullによるバグを防ぐための大切なサインです。
警告が出た場合は、次のような方法で安全に対処しましょう。
C#if (name is not null)
{
Console.WriteLine(name.Length);
}
C#string displayName = name ?? "未設定";
C#int length = name?.Length ?? 0;
また、非nullableプロパティは初期化方法を明確にすることが重要です。
C#public class User
{
public required string Name { get; set; }
public string? Memo { get; set; }
}
C# nullableを使いこなすポイントは、単に?や!の書き方を覚えることではありません。
大切なのは、「この値はnullになってよいのか」「nullの場合はどう処理するのか」「nullを許可しないならどこで初期化するのか」を設計として考えることです。
nullableを正しく使えば、NullReferenceExceptionを減らし、読みやすく安全なC#コードを書けるようになります。

