📘 .NET学習シリーズ 第1回 初心者向けガイド WPF + MVVM + Generic Host (.NET 10)

対象: JavaScriptの経験はあるが、.NETは初学者の方。プロジェクト: dotnetProjectForWindows_10

目次
  1. 何を作るの?(ゴール)
  2. 三層アーキテクチャ(視覚的に)
  3. ランタイムの流れ(1→2→3)
  4. JS開発者むけ対比表
  5. 依存性注入(DI)超要点
  6. フィルタの差し込み(拡張のコツ)
  7. どこを見ると何が分かる?(ファイルMAP)
  8. 実行と確認
  9. 用語ミニ辞典
  10. 付録(MVVM/DI/並列/エラー/JSON)

🎯1. 何を作るの?(ゴール)

画像フォルダを選んで、パイプライン(Source → Filters → Exporter)で一括処理します。初期は No-op(何もしない)ですが、ResizeFilter を追加して「幅600pxに縮小+JPEG化」を体験できます。UIには進捗/ログ表示、停止も備えます。

UI (WPF/MVVM)

  • 入力/出力フォルダを指定
  • 開始/停止、進捗バーとログ
  • 「アーキテクチャ」タブで構成を可視化

パイプライン

  • Source: フォルダ列挙
  • Filters: 連続適用(NoOp, Resizeなど)
  • Exporter: 保存(出力フォルダ)

Host/DI(依存性注入)

  • 「どのI/Fにどの実装か」を登録
  • Options(設定)やLoggerもここで配線

🏗️2. 三層アーキテクチャ(視覚的に)

Core(契約)

「差し込み口」(インターフェース)だけを定義。ここはめったに変えない。

  • IImageSourceProvider(入力列挙)
  • IImageFilter(画像加工)
  • IImageExporter(保存)
  • IProgressReporter(進捗通知)

Components(実装)

Coreの約束を具体化。差し替えや追加が容易。

  • FolderSource : 入力列挙
  • NoOpFilter / ResizeFilter : 画像加工
  • FileExporter : 保存

DesktopUI(UI+配線)

画面(XAML)と ViewModel、そして DI(依存性注入)の登録。

  • MainViewModel / 画面XAML
  • PipelineRunner(サービス)
  • App.xaml.cs(DI登録)

➡️3. ランタイムの流れ(1→2→3)

1) UIで入出力を指定 → Start
ViewModelのコマンドが実行され、PipelineRunner に処理依頼。
2) Runnerが Source→Filters→Exporter を順に実行
フォルダから画像列挙→各フィルタ適用→保存。
3) 進捗・エラーを IProgressReporter 経由でUIに反映
進捗バー/ログ/最新エラーが更新。

非同期とキャンセル対応(async/await + CancellationToken)。キュー(Channel)で安全に順次処理。

🔁4. JS開発者むけ対比表

JavaScriptの用語/慣習.NET(本プロジェクト)説明
DIコンテナ(NestJS等)Microsoft.Extensions.DependencyInjection「どのI/Fにどの実装か」を登録。App.xaml.csで行う。
Service/Providerサービス(PipelineRunner 等)UIから呼ばれる処理ロジック。テストしやすい。
Interface(型)Coreのインターフェース「差し込み口」。実装を疎結合にする。
Options/ConfigIOptions<T>設定(例: ResizeOptions)。DIで受け取る。
UI バインディング(Vue/React)WPF + MVVM バインディングXAMLとViewModelのプロパティを束ねる。
Async/Promiseasync/await + Task.NETの非同期の基本。キャンセルは CancellationToken

🔌5. 依存性注入(DI)超要点

// 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>();

🧩6. フィルタの差し込み(拡張のコツ)

  1. Componentsに IImageFilter 実装(例: WatermarkFilter)を作る
  2. App.xaml.csservices.AddSingleton<IImageFilter, WatermarkFilter>() を追加
  3. 登録順 = 実行順。NoOp → Resize → Watermark のように並ぶ

🗺️7. どこを見ると何が分かる?(ファイルMAP)

場所見るポイント
Core/Interfaces/*.cs契約(I/F)。変化が少ない=安定化の核。
Components/*.cs実装の本体。新機能はここに追加。
DesktopUI/App.xaml.csDI登録。どの実装が使われるか一目で分かる。
DesktopUI/MainViewModel.csUIの状態/コマンド。進捗/ログ/開始停止。
DesktopUI/MainWindow.xaml画面レイアウト。Tab「アーキテクチャ」もここ。
DesktopUI/Services/PipelineRunner.csSource→Filters→Exporter のパイプ制御。

▶️8. 実行と確認

  1. Visual Studio で ImagePipeline.sln を開く
  2. スタートアップを DesktopUI にして実行
  3. 入出力フォルダを指定 → 開始
  4. 「アーキテクチャ」タブ → 「最新の構成を読む」で現在の Source/Filters/Exporter を確認

📚9. 用語ミニ辞典(ざっくり)

🚀10. 次の一歩(おすすめ練習)

JS経験者向けの要点: 「I/Fで疎結合」「DIで差し替え」「MVVMでUIをバインド」。この3点を“繰り返し手で動かす”のが最短ルートです。


🧩付録A. MVVMの実コード(超入門)

WPFでは XAML(View)と ViewModel(C#)を「バインディング」で結びます。ボタンクリックはコマンド、テキストの表示はプロパティで繋ぎます。

XAML(抜粋)

<TextBox Text="{Binding InputFolder, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="開始" Command="{Binding StartCommand}" />
<ProgressBar Minimum="0"
             Maximum="{Binding TotalCount}"
             Value="{Binding ProcessedCount}"/>

ViewModel(抜粋)

[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] でボタンとメソッドを結ぶ
  • UIはロジックを知らない=テスト容易

🧲付録B. DI(依存性注入)の絵と落とし穴

考え方の絵

Contract(I/F)  ───────→  Implementation(実装)
      ▲                         ▲
      │                         │
      └────────────── Host(App.xaml.cs) が接着(登録)

落とし穴

🛠️付録C. PipelineRunner の内部(順次→並列の考え方)

現状は 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スレッドに注意。高頻度ならスロットリング/サンプリングも検討。

🧭付録D. エラー処理と通知(最小~発展)

段階方針ねらい
最小例外は catch → UIへログ表示落ちない・見える化
発展種類別集約/再試行/デッドレター回復性・保守性
将来構造化ログ・相関ID・メトリクス観測可能性

💾付録E. JSON保存(次の発展の前取り)

第1回の「受け入れ基準」の将来項目にある「JSON保存/復元」は、以下の方針で最小実装できます。

  1. Core: ISettingsStoreLoad/Save だけの契約)
  2. Components: JsonSettingsStoreEnvironment.SpecialFolder.ApplicationData配下に保存)
  3. DesktopUI: 起動時に Load → ViewModelに適用/終了時に Save
// 契約例
public interface ISettingsStore { T? Load<T>(string key); void Save<T>(string key, T value); }

設定値は InputFolder / OutputFolder / ResizeOptions など。第2回以降の Options とも親和性が高いです。