C#: Null許容型とパターンマッチング

1. Null 許容値型

値型は、デフォルトではnullにできません。Nullable<T>構造体(略記:T?)を使用することで、値型にNULLセマンティクスを付与することができます。

CSHARP
int? a = 42;
int? b = null;
bool? flag = null;
double? price = 9.99;

Console.WriteLine(a.HasValue);
Console.WriteLine(b.HasValue);
Console.WriteLine(flag.GetValueOrDefault());
Console.WriteLine(price.GetValueOrDefault(0.0));
TEXT 📖 参照専用
True
False
False
9.99

▶ サンプル

CSHARP
int? score = null;

Console.WriteLine(score.HasValue);
Console.WriteLine(score.GetValueOrDefault());
Console.WriteLine(score.GetValueOrDefault(60));

score = 88;
Console.WriteLine(score.Value);
▶ 試してみよう
TEXT 📖 参照専用
False
0
60
88

2. ヌル結合演算子とヌル条件演算子

??はデフォルト値を指定し、?.はメンバー変数に安全にアクセスし、!はコンパイラに対して「ここではnullではない」と伝えます。

CSHARP
int? x = null;
int y = x ?? 0;

string? name = null;
int? len = name?.Length;

string s = null!;
Console.WriteLine(s is null);
TEXT 📖 参照専用
True

▶ サンプル

CSHARP
string? input = null;

string display = input ?? "(empty)";
Console.WriteLine(display);

int? length = input?.Length;
Console.WriteLine(length ?? -1);

string? city = "Beijing";
Console.WriteLine(city?.Length ?? 0);
▶ 試してみよう
TEXT 📖 参照専用
(empty)
-1
7

3. ヌル許容参照型

C# 8 以降では、#nullable enableを使用することで、Null 許容参照型の警告を有効にできます。string?は Null を許可し、stringは許可しません。

CSHARP
#nullable enable
string? maybeNull = null;
string notNull = "hello";

maybeNull = null;
notNull = null!;

▶ サンプル

CSHARP
#nullable enable
string Greet(string? name)
{
    return name is null ? "Hello, stranger" : $"Hello, {name}";
}

Console.WriteLine(Greet(null));
Console.WriteLine(Greet("Alice"));
▶ 試してみよう
TEXT 📖 参照専用
Hello, stranger
Hello, Alice

4. ヌルチェックの戦略

戦略 使い方 シナリオ
明示的なチェック if (x is null) 分岐ロジックが必要
ヌルコアリシング x ?? defaultValue デフォルト値の指定
ヌル条件 x?.Member 安全なメンバーアクセス

▶ サンプル

CSHARP
string? GetName() => null;

string? name = GetName();

if (name is null)
{
    Console.WriteLine("Name is null");
}

string upper = name?.ToUpper() ?? "DEFAULT";
Console.WriteLine(upper);
▶ 試してみよう
TEXT 📖 参照専用
Name is null
DEFAULT

5. パターンマッチングの概要

パターンマッチングは、型チェックと変数抽出を一つに統合したもので、一般的にisswitchとともに使用されます。

(1) はパターンです

CSHARP
object obj = 42;

if (obj is int i)
{
    Console.WriteLine($"Is integer: {i}");
}
TEXT 📖 参照専用
Is integer: 42

▶ サンプル

CSHARP
object value = "hello";

if (value is int n)
{
    Console.WriteLine(n);
}
else if (value is string s)
{
    Console.WriteLine($"String length: {s.Length}");
}
▶ 試してみよう
TEXT 📖 参照専用
String length: 5

6. 型パターンとswitch文

switchでタイプ別に分岐し、変数を抽出します。

▶ サンプル

CSHARP
string Describe(object obj)
{
    return obj switch
    {
        int i => $"Integer {i}",
        double d => $"Double {d}",
        string s => $"String \"{s}\"",
        bool b => $"Boolean {b}",
        null => "null",
        _ => "Unknown type"
    };
}

Console.WriteLine(Describe(10));
Console.WriteLine(Describe(3.14));
Console.WriteLine(Describe("hi"));
Console.WriteLine(Describe(true));
▶ 試してみよう
TEXT 📖 参照専用
Integer 10
Double 3.14
String "hi"
Boolean True

7. プロパティ・パターン

プロパティパターンでは、比較のために手動で値を抽出することなく、オブジェクトのプロパティに基づいて照合が行われます。

▶ サンプル

CSHARP
var person = new { Name = "Alice", Age = 20 };

if (person is { Age: > 18 })
{
    Console.WriteLine("Adult");
}

string Category(object p) => p switch
{
    { Age: < 12 } => "Child",
    { Age: >= 12 and < 18 } => "Teenager",
    { Age: >= 18 } => "Adult",
    _ => "Unknown"
};

Console.WriteLine(Category(person));
▶ 試してみよう
TEXT 📖 参照専用
Adult
Adult

8. レコードタイプ

C# 9 では record が導入され、デフォルトで値ベースの等価比較と不変性が提供されるようになりました。

CSHARP
record Person(string Name, int Age);

▶ サンプル

CSHARP
record Person(string Name, int Age);

var p1 = new Person("Alice", 18);
var p2 = new Person("Alice", 18);

Console.WriteLine(p1 == p2);
Console.WriteLine(ReferenceEquals(p1, p2));
Console.WriteLine(p1);
▶ 試してみよう
TEXT 📖 参照専用
True
False
Person { Name = Alice, Age = 18 }

9. 式を使用する場合

withは、既存レコードのコピーを作成し、一部のプロパティを変更しますが、元のインスタンスは変更されません。

▶ サンプル

CSHARP
public class Student : IEquatable<Student>
{
    public string Name { get; }
    public int Age { get; }
    public string Grade { get; }
    public Student(string name, int age, string grade)
    {
        Name = name;
        Age = age;
        Grade = grade;
    }
    public Student With(int? age = null, string grade = null)
    {
        return new Student(Name, age ?? this.Age, grade ?? this.Grade);
    }
    public bool Equals(Student other)
    {
        return other != null && Name == other.Name && Age == other.Age && Grade == other.Grade;
    }
    public override bool Equals(object obj) => Equals(obj as Student);
    public override int GetHashCode() => System.HashCode.Combine(Name, Age, Grade);
    public override string ToString() => $"Student {{ Name = {Name}, Age = {Age}, Grade = {Grade} }}";
}

class Program
{
    static void Main()
    {
        var s1 = new Student("Bob", 20, "A");
        var s2 = s1.With(age: 21, grade: "A+");
        System.Console.WriteLine(s1);
        System.Console.WriteLine(s2);
    }
}
▶ 試してみよう
TEXT 📖 参照専用
Student { Name = Bob, Age = 20, Grade = A }
Student { Name = Bob, Age = 21, Grade = A+ }

10. タプル

タプルは複数の値を軽量な構造体にまとめ、名前付き要素や展開をサポートしています。

▶ サンプル

CSHARP
(int Id, string Name) person = (1, "Charlie");
Console.WriteLine(person.Id);
Console.WriteLine(person.Name);

var coords = (X: 3.0, Y: 4.0);
double distance = Math.Sqrt(coords.X * coords.X + coords.Y * coords.Y);
Console.WriteLine(distance);

(int x, int y) = (10, 20);
Console.WriteLine($"x={x}, y={y}");
▶ 試してみよう
TEXT 📖 参照専用
1
Charlie
5
x=10, y=20

11. タプルの展開とパターンマッチング

タプルとパターンマッチングを組み合わせることで、複数の条件を含む分岐を洗練された方法で処理できます。

▶ サンプル

CSHARP
string Classify(int score, bool hasBonus) => (score, hasBonus) switch
{
    (>= 90, true) => "Excellent+Bonus",
    (>= 90, false) => "Excellent",
    (>= 60, true) => "Pass+Bonus",
    (>= 60, false) => "Pass",
    _ => "Fail"
};

Console.WriteLine(Classify(95, true));
Console.WriteLine(Classify(75, false));
Console.WriteLine(Classify(50, true));
▶ 試してみよう
TEXT 📖 参照専用
Excellent+Bonus
Pass
Fail

❓ よくある質問

Q int?intを算術演算に直接使用できますか?
A いいえ。演算を行う前に、.Valueまたは??を使用して、nullではない値に変換する必要があります。
Q string?stringの違いは何ですか?
A string?ではnullの代入が可能ですが、stringでは、nullableが有効な状態でnullを代入するとコンパイラ警告が発生します。
Q レコードとクラスの根本的な違いは何ですか?
A レコードは値による等価比較(== で内容を比較)を使用するのに対し、クラスは参照による等価比較(== でメモリアドレスを比較)を使用します。
Q with 式は class と一緒に使用できますか?
A いいえ。with は、レコード型、または初期化のみのプロパティを持つ構造体でのみ使用できます。
Q タプルの要素数に制限はありますか?
A ValueTuple は最大 8 つの型パラメータをサポートしています。それ以上になる場合は、拡張のためにネストされた TRest が使用されます。

📖 まとめ

📝 練習問題

  1. double? price = null;を宣言し、GetValueOrDefault(99.9)を使用してデフォルト値を取得し、それを出力する
  2. string?というパラメータを受け取り、?.??を使用して文字列の長さを返す、あるいは-1を返すメソッドを作成してください。
  3. isというパターンを使って、object obj = 3.14;double dであるかどうかを確認し、d * 2を出力する
  4. record Book(string Title, double Price);を定義し、インスタンスを作成し、withを使用して価格を変更する
  5. タプルスイッチ式を使用して、(int month, int day)が「元旦」か「国慶節」かを判定する
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%