C# 複数EXE+共通DLLをどう管理する?―「1リポジトリ+1ソリューション」が効く条件と、大規模化したときの現実解
0. 結論(先に答え)
小〜中規模(チーム・プロジェクト数がまだ小さい)
-
1リポジトリ(モノレポ)にまとめ、1つのソリューション(.sln)に複数EXE+共通DLL(クラスライブラリ)を入れるのが、最も手戻りが少ない構成になりやすいです。
- 共通DLLへステップインできる(プロジェクト参照で開発できる)
- 参照箇所の洗い出しが IDE の機能で一発(Find All References / ソリューション全体検索)
- 「スタートアップ プロジェクト変更だけで Git が荒れる」問題は、通常は起きません(後述)
大規模(プロジェクト数が増え、ソリューションが重い・分業が進む)
-
モノレポは維持しつつ、次の「分割のしかた」でスケールさせるのが、現実的で安全です。
- **全体用の All.sln(または .slnx)**は1つ保つ(統合参照調査・統合ビルドの基準)
- 開発者の普段使いは **Solution Filter(.slnf)**で「必要な部分だけ読み込む」
- ビルド設定は Directory.Build.props / Directory.Build.targets で中央集権化
- どうしても複数リポジトリに分けるなら、“統合用のスーパーリポジトリ(サブモジュール)+統合ソリューション” を用意して、
git clone した時点でフォルダ構造を強制する(後述)
1. 何を死守したいのか(要件の整理)
ここで守りたいのは、次の2つです。
-
デバッガーで共通DLLのソースへステップインできること
→ これは「成果物DLL参照」ではなく プロジェクト参照(ProjectReference) が基本になります。 -
共通DLLのクラス/メソッドの参照箇所を、システム全体として半自動で一発抽出できること
→ Visual Studio の Find All References / ソリューション全体検索 / 参照の検索を、
**“システム全体を含む単一の視界”**で実行できる状態が重要です。
2. 「スタートアップ変更で .sln が汚れて Git が荒れる」問題は、通常は起きない
2-1. スタートアップ等の“ユーザー設定”は .suo(Solution User Options)に保存される
Visual Studio の **Startup Project(起動プロジェクト)**のような「ソリューションのプロパティ」は、通常 **.suo(Solution User Options)**側に保存されます。
Microsoft Learn(Solutions overview)には、.sln と .suo の役割が次のように説明されています(抜粋)。
公式(抜粋)
- 「The solution file contains text-based information … can be placed under source code control.」
- 「The solution user option file contains binary information … cannot be placed under source code control … user-specific information.」
つまり、起動プロジェクトを切り替えるだけで .sln が更新され、チーム全員に影響する——という心配は、通常は当たりません。
(※ .sln が更新されるのは、プロジェクト追加/削除や、ソリューション構成の追加など「共有すべき変更」を行った場合です。)
2-2. 「.suo / .vs をコミットしない」前提は、定番の .gitignore に組み込まれている
Visual Studio 系の定番 .gitignore として広く使われる GitHub の VisualStudio.gitignore では、.vs/ や *.suo が ignore 対象です(= “ユーザー固有”なので共有しない前提)。
テンプレート(抜粋)
.vs/*.suo
根拠URL(テンプレート):
https://raw.githubusercontent.com/github/gitignore/main/VisualStudio.gitignore
2-3. Visual Studio の「Git リポジトリ作成」では .gitignore を自動生成できる
Microsoft Learn(Azure Repos の Git 解説)には、Visual Studio で Git リポジトリを作成すると、Visual Studio 用の .gitignore を自動生成することが明記されています(抜粋)。
公式(抜粋)
- 「Visual Studio automatically creates a
.gitignorefile for the Visual Studio development environment when you create a Git repo.」
根拠URL(公式):
https://learn.microsoft.com/en-us/azure/devops/repos/git/ignore-files?view=azure-devops
結論:“起動プロジェクトを変えたらコミットが要る” ではなく、
「.suo/.vs をコミットしない」前提であれば、起動プロジェクトの変更だけで Git が荒れるのは避けられます。
3. 小〜中規模での推奨構成(最短で効く)
3-1. 構成の形(モノレポ+1ソリューション)
共通DLL(Class Library)も、各EXE(Console/WinForms/WPF/Service 等)も、同一リポジトリの同一ソリューションに含める形です。
3-2. これが効く理由(反論材料として使える“事実”)
-
プロジェクト参照にできる(Visual Studio の正式な機能)
- 公式: 参照の管理(Add Project Reference ができる)
- https://learn.microsoft.com/en-us/visualstudio/ide/managing-references-in-a-project?view=visualstudio
-
ステップインしやすい(成果物 DLL 参照より “中身が同じソース”を追いやすい)
- 公式: External Sources(外部ソースのデバッグ改善)
- ※プロジェクト参照なら「外部ソース」以前に、そもそも“同じソリューション内のプロジェクト”としてデバッグできます
-
参照箇所調査が一撃
- Find All References / ソリューション内検索の対象が 最初から全体
- 影響調査のコミュニケーションコストが下がります
4. 大規模になったらどうする?(理想:モノレポを維持しつつ“重さ”だけ逃がす)
大規模化で現実的に起きるのは、次の問題です。
- All.sln が巨大化して IDE の読み込みが重い
- 担当外のプロジェクトまで読み込む必要がない
- それでも「全体参照調査」「統合ビルド」は捨てたくない
このときの定石が、**Solution Filter(.slnf)**です。
4-1. Solution Filter(.slnf)で “読み込みだけ”分割する
4-1-1. Solution Filter(.slnf)の基礎(初心者向け)
**Solution Filter(拡張子 .slnf)は、「1つの .sln のうち、どのプロジェクトを“読み込むか”を保存するファイル」**です。
Microsoft Learn(Filtered solutions)では、**ソリューションフィルタで“必要なプロジェクトだけをロードして開く”**こと、
さらに .slnf を作成して共有/コミットできることが説明されています(抜粋)。
公式(抜粋)
- 「open a solution with only selective projects loaded」
- 「create a solution filter file … share … commit it to source control」
根拠URL(公式):
https://learn.microsoft.com/en-us/visualstudio/ide/filtered-solutions?view=visualstudio
また MSBuild の仕様として、.slnf は JSON 形式で、solution に “元の .sln” を指定する構造です(= .slnf は .sln の代替ではなく、.sln が必須)。
根拠URL(公式):
https://learn.microsoft.com/en-us/visualstudio/msbuild/solution-filters?view=visualstudio
4-1-2. 重要:全体参照調査(Find All References)を “YES” にする鍵は「全体を開く入口」を残すこと
Solution Filter は “軽く開く”ための仕組みです。
担当サブセットだけをロードした状態では、当然「未ロードのプロジェクト」まで含めた参照調査はできません。
そこで、全体参照調査を死守するために、次の 2種類の入口をリポジトリに固定します。
-
入口A:全体を読む入口(全体参照調査のため)
-
All.slnを開く(全プロジェクトをロード) - もしくは
Filters/AllLoaded.slnf(全プロジェクトを列挙した .slnf)を開く
→ この入口を開けば、共通DLLのクラス/メソッドの参照箇所をシステム全体で一発で洗い出せます。
-
-
入口B:普段を軽くする入口(作業効率のため)
-
Filters/TeamA.slnf/Filters/TeamB.slnfのように、担当サブセットだけを読む - 必要な時だけ「依存関係をロード」等で追加ロードする(Filtered solutions の手順に “Load project dependencies” が案内されています)
-
これで、先の質問「.slnf で全体参照調査は死守できるの?」に YES と言えます。
“全体を読む入口(All.sln / AllLoaded.slnf)” を同じリポジトリに固定しているからです。
4-2. さらに merge conflict を減らす(.slnx)
Microsoft は、新しいソリューション形式 .slnx を導入しており、
「読みやすさ」「差分の追いやすさ」「バージョン管理でのマージ衝突低減」などを目的にしています。
- https://devblogs.microsoft.com/visualstudio/new-simpler-solution-file-format/
- https://devblogs.microsoft.com/dotnet/introducing-slnx-support-dotnet-cli/
※既存の .sln 運用をすぐ捨てる必要はありません。
ただし、「巨大ソリューションを複数人で触る」ほど 差分衝突のコストが効いてくるため、検討価値があります。
4-3. ビルド設定・参照の統一は Directory.Build.props で“構造的に強制”する
大規模になるほど「プロジェクトごとに設定がバラバラ」が事故になります。
MSBuild は Directory.Build.props / Directory.Build.targets による フォルダ単位の共通設定を公式にサポートしています。
これは リポジトリ直下に置けば、git clone の時点で構造的に強制できます。
5. 「1リポジトリ = 1ソリューション = 1プロジェクト」を押し付けられた時の現実解
ここで問題になるのは、次の2点です。
- 統合的な参照箇所調査(IDE一発)が難しくなる
- 共通DLLへのステップインが難しくなる(成果物DLL参照に寄りやすい)
5-1. それでも“構造で強制”し、IDE一発調査もステップインも守る:スーパーリポジトリ+サブモジュール
5-1-1. 「スーパーリポジトリ(superproject)」「サブモジュール」の基礎(初心者向け)
スーパーリポジトリ(superproject)とは
**サブモジュールを含む“親リポジトリ”**のことです。
この親リポジトリの中に、別リポジトリを 指定パスに固定配置できます(例:modules/CommonRepo)。
その結果、git clone した時点でディレクトリ構造が決まり、相対パス参照が壊れにくくなります。
サブモジュール(submodule)とは
Git が公式に提供する仕組みで、**「別リポジトリを、親リポジトリの特定ディレクトリに“埋め込む”」**ための機能です。
親リポジトリは、各サブモジュールについて どのコミット(どの版)を使うかを記録します。
clone するときに必要なコマンド(ここが重要)
Git Submodule を採用したリポジトリは、通常の git clone だけでは submodule の中身が揃いません。
submodule を初期化して、内容を取得する操作が必須です。
- 最初の clone で一発で揃える(推奨)
git clone --recurse-submodules <superproject-url>
(古い書き方として --recursive もあります)
git clone --recursive <superproject-url>
- すでに clone 済みなら、あとから揃える
git submodule update --init --recursive
- pull した後、submodule の更新も揃える(運用として固定する)
git pull
git submodule update --init --recursive
根拠URL(公式):
https://git-scm.com/docs/git-clone
https://git-scm.com/docs/git-submodule
https://git-scm.com/book/en/v2/Git-Tools-Submodules
公式(抜粋・要点)
git cloneの--recurse-submodulesは、clone 時に submodule を初期化/更新するオプション。git submodule update --init --recursiveは、未初期化 submodule を初期化して取得し、入れ子も含めて更新する。
注意(大規模で“やるなら”ここまで決める)
- サブモジュール運用は、**更新手順(どのタイミングで superproject 側の参照コミットを進めるか)が必要になります。
ただしそれでも、「統合調査用 All.sln を保持し、構造は clone で強制」**という目的には強力です。
複数リポジトリに分ける事情がある場合でも、
「統合調査用の入口」を 別リポジトリとして用意し、各リポジトリを サブモジュールで固定配置します。
Git はサブモジュールを「別リポジトリを親リポジトリのサブディレクトリに埋め込む」仕組みとして定義しています。
- 公式(抜粋・要点)
Git Book Submodules):https://git-scm.com/book/en/v2/Git-Tools-Submodules - 公式(抜粋・要点)
gitsubmodules):https://git-scm.com/docs/gitsubmodules
この方法だと、git clone した時点でディレクトリ構造が決まり、相対パスが壊れにくい(=ルールではなく構造で強制)というメリットがあります。
重要
- 個別リポジトリを残しつつ、統合の“見える化”と参照調査を取り戻せます
- 「共通DLLにステップイン」も ProjectReference で守れます
- “統合用ソリューション”を触るのはスーパーリポ側なので、個別リポの運用ポリシーとも切り分けやすいです
5-2. 成果物DLL参照を採る場合でも、ステップインを(ある程度)取り戻す:Source Link / External Sources
もし「共通DLLは NuGet 化して配布する」などの事情で、成果物参照が避けられない場合は、
Visual Studio の External Sources や Source Link により、外部依存のソースをデバッグで追いやすくする方向があります。
ただし、この方法は「IDEの参照箇所調査を“システム全体で一発”」という要求を満たしにくいので、
統合ソリューション(All.sln)を別途維持するのが安全です。
6. まとめ(反論材料の形に落とす)
-
“共通DLLにステップイン”と “参照箇所をIDE一発で洗い出し”を同時に満たすなら、基本は
同一リポジトリ+同一ソリューションに全プロジェクトを含め、ProjectReference でつなぐのが最短。 -
スタートアップ設定で Git が荒れるは、通常は **.suo(ユーザー設定)**の領域なので、
.gitignoreで除外していれば共有されません。 -
大規模化したら、モノレポを崩す前に
**Solution Filter(.slnf)+ Directory.Build.props +(必要なら .slnx)**でスケールさせるのが現実的。 -
どうしても複数リポジトリなら、
スーパーリポ+サブモジュール+統合ソリューションで「clone時点で構造を強制」しつつ、
IDE一発調査+ステップインを守る。
参考リンク(一次情報中心)
- Visual Studio: Project / Solution properties(.suo について)
https://learn.microsoft.com/en-us/visualstudio/ide/managing-project-and-solution-properties?view=visualstudio - Azure DevOps: ignore files(VS が .gitignore を作ること)
https://learn.microsoft.com/en-us/azure/devops/repos/git/ignore-files?view=azure-devops - Visual Studio: Create Git repository(.gitignore テンプレート)
https://learn.microsoft.com/en-us/visualstudio/version-control/git-create-repository?view=visualstudio - Visual Studio: Filtered solutions / Solution filters(.slnf)
https://learn.microsoft.com/en-us/visualstudio/ide/filtered-solutions?view=visualstudio
https://learn.microsoft.com/en-us/visualstudio/msbuild/solution-filters?view=visualstudio - MSBuild: Directory.Build.props / Directory.Build.targets
https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory?view=visualstudio - Visual Studio Blog: External Sources(外部依存のソースをデバッグしやすく)
https://devblogs.microsoft.com/visualstudio/debugging-external-sources-with-visual-studio/ - Visual Studio Blog: new solution file format (.slnx)
https://devblogs.microsoft.com/visualstudio/new-simpler-solution-file-format/ - .NET Blog: dotnet CLI の slnx サポート
https://devblogs.microsoft.com/dotnet/introducing-slnx-support-dotnet-cli/ - Git: .gitignore / submodules(公式)
https://git-scm.com/docs/gitignore
https://git-scm.com/book/en/v2/Git-Tools-Submodules
https://git-scm.com/docs/gitsubmodules
Discussion