対象: JavaScriptの経験はあるが、.NETは初学者の方。プロジェクト: dotnetProjectForWindows_10
画像フォルダを選んで、パイプライン(Source → Filters → Exporter)で一括処理します。初期は No-op(何もしない)ですが、ResizeFilter を追加して「幅600pxに縮小+JPEG化」を体験できます。UIには進捗/ログ表示、停止も備えます。
「差し込み口」(インターフェース)だけを定義。ここはめったに変えない。
IImageSourceProvider(入力列挙)IImageFilter(画像加工)IImageExporter(保存)IProgressReporter(進捗通知)Coreの約束を具体化。差し替えや追加が容易。
FolderSource : 入力列挙NoOpFilter / ResizeFilter : 画像加工FileExporter : 保存画面(XAML)と ViewModel、そして DI(依存性注入)の登録。
MainViewModel / 画面XAMLPipelineRunner(サービス)App.xaml.cs(DI登録)PipelineRunner に処理依頼。非同期とキャンセル対応(async/await + CancellationToken)。キュー(Channel)で安全に順次処理。
| JavaScriptの用語/慣習 | .NET(本プロジェクト) | 説明 |
|---|---|---|
| DIコンテナ(NestJS等) | Microsoft.Extensions.DependencyInjection | 「どのI/Fにどの実装か」を登録。App.xaml.csで行う。 |
| Service/Provider | サービス(PipelineRunner 等) | UIから呼ばれる処理ロジック。テストしやすい。 |
| Interface(型) | Coreのインターフェース | 「差し込み口」。実装を疎結合にする。 |
| Options/Config | IOptions<T> | 設定(例: ResizeOptions)。DIで受け取る。 |
| UI バインディング(Vue/React) | WPF + MVVM バインディング | XAMLとViewModelのプロパティを束ねる。 |
| Async/Promise | async/await + Task | .NETの非同期の基本。キャンセルは CancellationToken。 |
App.xaml.cs で宣言(登録)。IOptions<ResizeOptions> で注入(services.Configure<T>)。// DesktopUI/App.xaml.cs(抜粋)
services.AddSingleton<IImageSourceProvider, FolderSource>();
services.AddSingleton<IImageFilter, NoOpFilter>();
services.Configure<ResizeOptions>(o => { o.TargetWidth = 600; o.JpegQuality = 90; });
services.AddSingleton<IImageFilter, ResizeFilter>();
services.AddSingleton<IImageExporter, FileExporter>();
IImageFilter 実装(例: WatermarkFilter)を作るApp.xaml.cs で services.AddSingleton<IImageFilter, WatermarkFilter>() を追加| 場所 | 見るポイント |
|---|---|
Core/Interfaces/*.cs | 契約(I/F)。変化が少ない=安定化の核。 |
Components/*.cs | 実装の本体。新機能はここに追加。 |
DesktopUI/App.xaml.cs | DI登録。どの実装が使われるか一目で分かる。 |
DesktopUI/MainViewModel.cs | UIの状態/コマンド。進捗/ログ/開始停止。 |
DesktopUI/MainWindow.xaml | 画面レイアウト。Tab「アーキテクチャ」もここ。 |
DesktopUI/Services/PipelineRunner.cs | Source→Filters→Exporter のパイプ制御。 |
ImagePipeline.sln を開くIOptions<T>)。ResizeOptions の幅を変更(スライダー連動)WatermarkFilter を追加JS経験者向けの要点: 「I/Fで疎結合」「DIで差し替え」「MVVMでUIをバインド」。この3点を“繰り返し手で動かす”のが最短ルートです。
WPFでは XAML(View)と ViewModel(C#)を「バインディング」で結びます。ボタンクリックはコマンド、テキストの表示はプロパティで繋ぎます。
<TextBox Text="{Binding InputFolder, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="開始" Command="{Binding StartCommand}" />
<ProgressBar Minimum="0"
Maximum="{Binding TotalCount}"
Value="{Binding ProcessedCount}"/>
[ObservableProperty] private string _inputFolder = string.Empty;
[ObservableProperty] private int _totalCount;
[ObservableProperty] private int _processedCount;
[RelayCommand]
private async Task StartAsync()
{
await _runner.RunAsync(InputFolder, OutputFolder, this, _cts.Token);
}
[ObservableProperty]でプロパティ変更がUIに反映[RelayCommand] でボタンとメソッドを結ぶ考え方の絵
Contract(I/F) ───────→ Implementation(実装)
▲ ▲
│ │
└────────────── Host(App.xaml.cs) が接着(登録)
new しない)落とし穴
現状は 1ワーカ(順次)。2本にするには、同じChannelを複数のワーカTaskで読み取ればOK。
// 例: 2ワーカにするイメージ(擬似コード)
var workerTasks = Enumerable.Range(0, 2).Select(_ => Task.Run(async () => {
await foreach (var path in channel.Reader.ReadAllAsync(ct)) {
// 読み・変換・書き出し
}
})).ToArray();
await Task.WhenAll(producer, Task.WhenAll(workerTasks));
UI更新はDispatcherスレッドに注意。高頻度ならスロットリング/サンプリングも検討。
| 段階 | 方針 | ねらい |
|---|---|---|
| 最小 | 例外は catch → UIへログ表示 | 落ちない・見える化 |
| 発展 | 種類別集約/再試行/デッドレター | 回復性・保守性 |
| 将来 | 構造化ログ・相関ID・メトリクス | 観測可能性 |
第1回の「受け入れ基準」の将来項目にある「JSON保存/復元」は、以下の方針で最小実装できます。
ISettingsStore(Load/Save だけの契約)JsonSettingsStore(Environment.SpecialFolder.ApplicationData配下に保存)// 契約例
public interface ISettingsStore { T? Load<T>(string key); void Save<T>(string key, T value); }
設定値は InputFolder / OutputFolder / ResizeOptions など。第2回以降の Options とも親和性が高いです。