今回から数回に分けて、.NET のクラスライブラリ、アセンブリ、構成、NuGet、発行について整理します。
まずこの記事では、C# の 名前空間 と アセンブリ の基本を扱います。
名前空間はソースコード上の型の分類、アセンブリはビルド後にできる .dll や .exe の単位です。
この 2 つを分けて理解しておくと、クラスライブラリの作成、参照、NuGet パッケージ化がずっと見通しやすくなります。
名前空間とは
名前空間は、型の名前を整理するための仕組みです。
たとえば、Customer というクラス名は多くのプロジェクトで使われます。
そのままでは名前が衝突しやすいため、MyApp.Domain.Customer や MyApp.Api.Customer のように、名前空間で意味のまとまりを作ります。
namespace MyApp.Domain;
public class Customer
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
C# 10 以降では、上のようなファイル スコープ名前空間を使えます。
従来の波括弧を使う書き方もできます。
namespace MyApp.Domain
{
public class Customer
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
}
どちらも意味はほぼ同じです。
1 ファイルに 1 つの名前空間を置くなら、ファイル スコープ名前空間の方がすっきり書けます。
完全修飾名で名前衝突を避ける
同じ型名が複数の名前空間にある場合、完全修飾名を使うと区別できます。
namespace Sales;
public class Report
{
public string Title => "売上レポート";
}
namespace Inventory;
public class Report
{
public string Title => "在庫レポート";
}
呼び出し側では、次のように名前空間を含めて指定できます。
Sales.Report salesReport = new Sales.Report();
Inventory.Report inventoryReport = new Inventory.Report();
Console.WriteLine(salesReport.Title);
Console.WriteLine(inventoryReport.Title);
完全修飾名は少し長くなりますが、「どの型を使っているか」が明確になります。
using エイリアスで読みやすくする
完全修飾名が長い場合は、using エイリアスを使うと読みやすくできます。
using SalesReport = Sales.Report;
using InventoryReport = Inventory.Report;
SalesReport salesReport = new SalesReport();
InventoryReport inventoryReport = new InventoryReport();
Console.WriteLine(salesReport.Title);
Console.WriteLine(inventoryReport.Title);
エイリアスは、名前衝突を避けながらコードを短く保つための手段です。
たとえば、外部ライブラリと自分のプロジェクトで同じ型名が出てきたときにも使えます。
using DomainUser = MyApp.Domain.User;
using IdentityUser = Microsoft.AspNetCore.Identity.IdentityUser;
入れ子の名前空間
名前空間は階層的に表現できます。
namespace MyApp.Features.Orders;
public class OrderService
{
public void Submit()
{
Console.WriteLine("注文を送信しました。");
}
}
MyApp.Features.Orders は、物理フォルダー構成と合わせると読みやすくなります。
MyApp/
Features/
Orders/
OrderService.cs
ただし、C# の名前空間はフォルダー構成と自動的に連動するわけではありません。
フォルダーと名前空間を揃えるのは、あくまで人間が理解しやすくするための慣習です。
プロジェクトの既定名前空間
.NET プロジェクトには、既定の名前空間に相当する設定があります。
SDK スタイルのプロジェクトでは、通常プロジェクト名が使われます。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>MyCompany.MyProduct</RootNamespace>
</PropertyGroup>
</Project>
RootNamespace を指定すると、Visual Studio などで新しいクラスを追加したときの名前空間の基準になります。
C# では、名前空間はソースコードに明示的に書かれたものが使われます。
RootNamespace は、主にテンプレート生成や IDE の補助として理解するとよいです。
アセンブリとは
アセンブリは、.NET のビルド成果物です。
通常は次のどちらかのファイルとして出力されます。
.dll: クラスライブラリなど、他のアプリから参照されることが多い.exe: 実行可能なアプリケーション
名前空間はソースコード上の分類ですが、アセンブリは実際に配布・読み込みされる単位です。
1 つのアセンブリに複数の名前空間を含めることもできます。
逆に、同じ名前空間の型が複数のアセンブリに分かれて存在することもあります。
アセンブリが担う役割
アセンブリには、主に次の役割があります。
コードの再利用単位になる
共通処理をクラスライブラリとしてまとめると、複数のアプリから参照できます。
namespace MyCompany.Text;
public static class TextFormatter
{
public static string Surround(string value)
{
return $"[{value}]";
}
}
このクラスをライブラリに入れておけば、コンソールアプリ、Web アプリ、テストプロジェクトから同じ処理を使えます。
型の境界を作る
アセンブリは、public や internal の境界にも関係します。
public class PublicService
{
}
internal class InternalHelper
{
}
public 型は他のアセンブリから参照できます。
internal 型は、基本的に同じアセンブリ内からだけ参照できます。
バージョン管理の単位になる
アセンブリにはバージョン情報を持たせることができます。
NuGet パッケージとして配布するときも、アセンブリやパッケージのバージョンは重要です。
<PropertyGroup>
<Version>1.2.0</Version>
<AssemblyVersion>1.2.0.0</AssemblyVersion>
<FileVersion>1.2.0.0</FileVersion>
</PropertyGroup>
どのバージョンのライブラリを使っているかを明確にできるため、依存関係の管理がしやすくなります。
自己記述的な情報を持つ
.NET アセンブリには、型、メソッド、属性、参照アセンブリなどのメタデータが含まれています。
そのため、リフレクションを使って実行時に型情報を調べられます。
using System.Reflection;
Assembly assembly = typeof(string).Assembly;
Console.WriteLine(assembly.FullName);
foreach (Type type in assembly.GetTypes().Take(5))
{
Console.WriteLine(type.FullName);
}
アセンブリが自己記述的であることは、リフレクション、シリアライザー、DI コンテナー、テストランナーなどの仕組みを支えています。
まとめ
名前空間は、C# の型を論理的に分類するための仕組みです。
完全修飾名やエイリアスを使うことで、名前衝突を避けながらコードを読みやすくできます。
一方、アセンブリはビルド後の配布・参照・読み込みの単位です。
名前空間とアセンブリは似て見えますが、役割は別です。
次回は、.NET アセンブリの内部構造、クラスライブラリとコンソールアプリの違い、対象フレームワークの考え方を見ていきます。