第41章:Composite ②:身近な例で理解(フォルダ階層の発想)📁
ねらい 🎯✨
- Composite(コンポジット)が「ツリー構造を同じ扱いにする」って感覚を、フォルダ階層でスッと理解する🌳💡
- .NET の DirectoryInfo / FileInfo / FileSystemInfo を読みながら、「どこがCompositeっぽい?」を見抜けるようになる👀📚
到達目標 ✅🌸
- 「File(葉) と Directory(枝) を、同じ型(基底)として扱える理由」を説明できる🙂
DirectoryInfoから子要素を列挙して、同じ処理(表示/集計) をツリー全体に適用できる🧩✨- 「
Get...とEnumerate...の違い」を、使いどころと一緒に言える📦🚶♀️
手順 🧭🛠️
1) まず「Compositeの登場人物」をフォルダで当てはめる 🧩🌳

Compositeはざっくりこう👇
- Component(共通インターフェイス/基底):共通で扱える型
- Leaf(葉):子を持たない
- Composite(枝):子を持つ(中にぶら下がる)
ファイルシステムだとこうなるよ👇
- Component:
FileSystemInfo(FileInfoとDirectoryInfoの共通の基底クラス)(Microsoft Learn) - Leaf:
FileInfo(ファイル) - Composite:
DirectoryInfo(フォルダ。中身=子要素を持てる)
この「共通の型で、ファイルもフォルダも受け取れる」って時点で、Compositeの空気が出てる☺️✨
2) 子要素を取るAPIを “読む”(ここが .NET のCompositeっぽさ)📚👀
DirectoryInfo は、子要素(ファイル/フォルダ)を まとめて FileSystemInfo として返せる よ🙌
EnumerateFileSystemInfos():列挙(IEnumerable<FileSystemInfo>)(Microsoft Learn)GetFileSystemInfos():配列で一括取得(FileSystemInfo[])(Microsoft Learn)
つまり、呼び出し側は 「それがファイルかフォルダか」だけを必要なときに見分ければOK。 構造(ツリー)を “同じ扱い” に近づけられるってことだね🙂🌿
3) ミニ実装:フォルダツリーを「同じ操作」で表示する 📁➡️🌳
ポイントはこれ👇
- 子要素は
FileSystemInfoとして受け取る(= Component) DirectoryInfoだったら再帰(= Compositeとして子へ)FileInfoなら表示だけ(= Leaf)
using System;
using System.IO;
public static class Program
{
public static void Main(string[] args)
{
var path = args.Length > 0 ? args[0] : Environment.CurrentDirectory;
var root = new DirectoryInfo(path);
if (!root.Exists)
{
Console.WriteLine($"見つからないよ: {root.FullName}");
return;
}
PrintTree(root, depth: 0, maxDepth: 3);
}
private static void PrintTree(DirectoryInfo dir, int depth, int maxDepth)
{
if (depth > maxDepth) return;
Console.WriteLine($"{Indent(depth)}📁 {dir.Name}");
try
{
foreach (var entry in dir.EnumerateFileSystemInfos())
{
// 取得したメタ情報が古い可能性があるので更新(必要なときだけでOK)
entry.Refresh();
// ジャンクション/シンボリックリンク等で無限ループしやすいので軽く回避
if ((entry.Attributes & FileAttributes.ReparsePoint) != 0)
{
Console.WriteLine($"{Indent(depth + 1)}🪝 {entry.Name}(リンク系っぽいのでスキップ)");
continue;
}
switch (entry)
{
case DirectoryInfo childDir:
PrintTree(childDir, depth + 1, maxDepth);
break;
case FileInfo file:
Console.WriteLine($"{Indent(depth + 1)}📄 {file.Name}");
break;
default:
Console.WriteLine($"{Indent(depth + 1)}❓ {entry.Name}");
break;
}
}
}
catch (UnauthorizedAccessException)
{
Console.WriteLine($"{Indent(depth + 1)}🔒 アクセスできないフォルダなので中身は見れなかったよ");
}
catch (DirectoryNotFoundException)
{
Console.WriteLine($"{Indent(depth + 1)}⚠️ 途中でフォルダが消えた/移動したみたい");
}
catch (IOException ex)
{
Console.WriteLine($"{Indent(depth + 1)}⚠️ IOエラー: {ex.Message}");
}
}
private static string Indent(int depth) => new string(' ', depth * 2);
}
DirectoryInfo.Exists で存在確認できるよ(Microsoft Learn)。
あと FileSystemInfo.Attributes を見るときは、取得した値がキャッシュされることがあるので、必要なら Refresh() を使うのが安心🙆♀️(Microsoft Learn)
4) もう一歩:ツリー全体に「同じルール」をかける(更新日時でフィルタ)⏰🔎
Compositeが嬉しいのは、「構造(ツリー) と 操作(やりたいこと) を分けやすい」こと!
たとえば「最終更新が古いものだけ見たい」ってとき👇
LastWriteTimeUtc も FileSystemInfo 側にあるから、ファイル/フォルダを同じ目線で扱えるよ✨(Microsoft Learn)
- 同じ型で受ける(=
FileSystemInfo) - 同じ条件で判定(更新日時)
- 必要なら
Refresh()(値が古い可能性)(Microsoft Learn)
(実装は3) の entry.Refresh() → entry.LastWriteTimeUtc を見て continue するだけでOKだよ🙂🌸)
5) Get... と Enumerate... の使い分け(実務で超大事)📦⚡
-
GetFileSystemInfos()は 配列で全部取る(一括)(Microsoft Learn)- 小さめのフォルダなら手軽✨
-
EnumerateFileSystemInfos()は 列挙で順に取る(foreachで流せる)(Microsoft Learn)- 件数が多いところで強い💪
「まず1件だけ探して見つかったら止めたい」みたいなときは、Enumerate... の方が相性いいことが多いよ😊
よくある落とし穴 ⚠️😵💫
- リンク系(ジャンクション/シンボリックリンク)で無限ループ:
ReparsePointを雑にでも避けるのが安全🪝 - アクセス拒否で例外が飛ぶ:
UnauthorizedAccessExceptionは普通に起きるもの🔒 - 巨大フォルダで
Get...一括→重い:件数が多そうならEnumerate...を優先しよ🙂 - メタ情報が古いまま使っちゃう:
AttributesやLastWriteTimeUtcは必要ならRefresh()を挟む🙆♀️(Microsoft Learn) - “Composite用の自作ツリークラス”を作り始める:今回は標準クラスで体感する回だから、まずは
DirectoryInfo/FileInfoで十分だよ🧘♀️✨
演習 📝🎀(10〜30分)
- 深さ制限を変えてみよう
maxDepthを 1 / 2 / 5 に変えて、表示の変化を確認👀
- ツリー集計:ファイル数とフォルダ数を数えよう
FileSystemInfoを受け取って、DirectoryInfoなら再帰、FileInfoならカウント加算📊
- “更新が古いものだけ”表示
- 「30日より古い」みたいな条件でフィルタして、古い順に出す(できたら最高💯✨)
チェック ✅💖
DirectoryInfoから取れる子要素を、共通の型FileSystemInfoで扱えるって説明できる?(Microsoft Learn)- “葉(File)” と “枝(Directory)” を 同じ処理の枠で回せるって、コードで見せられる?🌳
GetFileSystemInfos()とEnumerateFileSystemInfos()の違い(配列/列挙)を言える?(Microsoft Learn)- アクセス拒否やリンクで詰まらない工夫(例外処理/
ReparsePoint)が入ってる?🔒🪝