上一章奠定命名空間基礎,本章聚焦進階實戰場景:型別別名在泛型/委派/元組中的威力、跨組件類型轉發實現版本遷移、 `extern alias` 解決鑽石相依導致的同名型別衝突、Source Generator 隱含命名空間生成等現代技術。掌握這些技巧,能從容應對大型專案、第三方套件整合、舊系統重構等複雜挑戰。
// 傳統:冗長難讀
System.Collections.Generic.Dictionary>> cache;
// 別名:語意清晰
using StringList = System.Collections.Generic.List;
using KeyValue = System.Tuple;
using CacheDict = System.Collections.Generic.Dictionary;
var cache = new CacheDict();
// 委派別名
using EventHandler = System.EventHandler;
using AsyncCallback = System.Func;
// C# 12+ 函數指標別名 (unsafe context)
using Callback = delegate*;
using Comparer = delegate*;
// 使用
Callback cb = &MyMethod;
Comparer cmp = &CompareInts;
// 元組別名 (C# 7.0+)
using Coord = (int X, int Y);
using Person = (string Name, int Age, string Email);
// 記錄型別別名 (C# 9.0+)
using UserDto = UserRecord; // 引用現有 record
using Result = (bool Success, string Message); // 匿名記錄別名
// 解構與模式比對
var (x, y) = GetCoord();
if (person is (_, >= 18, _)) { /* 成年 */ }
// 解決多專案同名命名空間衝突
using CoreV1 = MyCompany.Core.V1;
using CoreV2 = MyCompany.Core.V2;
// 縮短深層命名空間
using Repo = MyCompany.ECommerce.Infrastructure.Repositories;
using Svc = MyCompany.ECommerce.Application.Services;
using Dto = MyCompany.ECommerce.Contracts.Dtos;
// 避開關鍵字衝突
using @class = MyCompany.Reflection.Metadata.ClassInfo;
using @namespace = MyCompany.Reflection.Metadata.NamespaceInfo;
重構時將型別從 `AssemblyA` 移至 `AssemblyB`,但現有消費端仍參考 `AssemblyA`。若直接移動,消費端重新編譯會報錯「找不到型別」。
// AssemblyA (舊組件,保留相容性)
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(MyCompany.Core.Services.UserService))]
[assembly: TypeForwardedTo(typeof(MyCompany.Core.Models.Order))]
// AssemblyB (新組件,實際實作)
namespace MyCompany.Core.Services
{
public class UserService { /* 實作 */ }
}
namespace MyCompany.Core.Models
{
public class Order { /* 實作 */ }
}
| 場景 | 可行性 | 說明 |
|---|---|---|
| 型別跨組件移動 | ✅ 完美支援 | 類別、結構、介面、委派、列舉 |
| 泛型型別定義移動 | ✅ 支援 | `TypeForwardedTo(typeof(List<>))` |
| 靜態類別/擴充方法 | ⚠️ 部分支援 | 擴充方法需同時轉發定義類別 |
| 巢狀型別 | ✅ 支援 | 逐個轉發 |
| 同組件內重命名 | ❌ 不適用 | 需原始碼層面重命名 |
// 專案結構
MyApp
├── LibA (參考 CoreLib 1.0)
└── LibB (參考 CoreLib 2.0)
// CoreLib 1.0 與 2.0 含有同名型別:CoreLib.Configuration.Settings
// MyApp 同時參考 LibA、LibB → 型別衝突:哪個 Settings?
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="CoreLib" Version="1.0" >
<Aliases>CoreV1</Aliases>
</PackageReference>
<PackageReference Include="CoreLib" Version="2.0" >
<Aliases>CoreV2</Aliases>
</PackageReference>
</ItemGroup>
</Project>
extern alias CoreV1;
extern alias CoreV2;
// 使用完整限定名稱
var settings1 = new CoreV1::CoreLib.Configuration.Settings();
var settings2 = new CoreV2::CoreLib.Configuration.Settings();
// 別名搭配 using
using SettingsV1 = CoreV1::CoreLib.Configuration.Settings;
using SettingsV2 = CoreV2::CoreLib.Configuration.Settings;
var s1 = new SettingsV1();
var s2 = new SettingsV2();
// 定義共用介面 (獨立組件 SharedContracts)
public interface ISettings { string GetValue(string key); }
// LibA 實作
public class SettingsV1Adapter : ISettings
{
private readonly CoreV1::CoreLib.Configuration.Settings _inner = new();
public string GetValue(string key) => _inner.Get(key);
}
// LibB 實作
public class SettingsV2Adapter : ISettings
{
private readonly CoreV2::CoreLib.Configuration.Settings _inner = new();
public string GetValue(string key) => _inner.Read(key);
}
// MyApp 僅依賴 ISettings,透過 DI 注入具體實作
// 完全避開型別衝突,實現架構解耦
[Generator]
public class NamespaceGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 掃描專案結構,自動產生對應命名空間的介面/類別
var provider = context.SyntaxProvider
.CreateSyntaxProvider(
(node, _) => node is ClassDeclarationSyntax,
(ctx, _) => GetNamespaceInfo(ctx))
.Where(m => m != null);
context.RegisterSourceOutput(provider, (ctx, info) =>
{
var code = $$"""
namespace {{info.Namespace}};
public interface I{{info.Name}}Repository
{
Task<{{info.Name}}> GetByIdAsync(int id);
Task AddAsync({{info.Name}} entity);
}
""";
ctx.AddSource($"I{info.Name}Repository.g.cs", code);
});
}
}
| 選項 | 效果 |
|---|---|
| `/rootnamespace` | 設定專案預設根命名空間 (VS 專案屬性自動對應) |
| `/namespace` (C# 11+) | 命令列指定檔案範圍命名空間 |
| `nowarn:CS0108` | 抑制「隱藏繼承成員」警告 (謹慎使用) |
| `warnaserror:CS0108` | 視為錯誤,強制加上 `new` 關鍵字 |
SA1402:檔案只能包含單一型別 (配合檔案範圍命名空間)SA1403:檔案只能包含單一命名空間IDE0008:使用隱含型別 (var) / 明確型別RCS1102:使用 `using static` 簡化靜態成員存取# dotnet-format 內建支援
dotnet format --folder --include-generated
# 或使用 Roslynator 命令列
dotnet roslynator fix --scope solution
# ADR-003: 命名空間與組件對齊策略
## 狀態: Accepted
## 背景
專案從單體遷移至模組化單體,需明確命名空間與組件邊界。
## 決策
1. 一組件 = 一根命名空間 (Company.Product.Module)
2. 公開 API 僅暴露根命名空間下的介面/DTO
3. 內部實作置於 .Internal 子命名空間,標記 internal
4. 跨組件通訊僅透過介面/事件,禁止直接參考具體類別
5. 採用檔案範圍命名空間、global using、CPM 管理版本
## 後果
+ 清晰邊界、低耦合、易重構
- 初期需嚴格 Code Review 執行
- 共用核心組件版本升級需協調
命名空間不只是「加個前綴避免衝突」,而是 .NET 類型系統的**架構邊界表達工具**。從別名簡化複雜泛型、類型轉發實現無縫重構、 `extern alias` 破解鑽石相依、Source Generator 自動化結構生成,到 ADR 落地團隊共識,每個技術點都直指軟體工程核心痛點:如何在演進中保持架構清晰、依賴可控、重構安全。將命名空間視為架構治理的一等公民,投入設計思考,長期收益將遠超初期成本。