C#のinternalとpublicの違いを徹底解説|アクセス修飾子の使い分けと実例

はじめに

C#でクラスやメソッドを定義するときによく使うアクセス修飾子に、internalpublicがあります。

どちらも「どこからその型やメンバーにアクセスできるか」を決めるためのキーワードですが、違いを曖昧なまま使っていると、別プロジェクトから参照できない、ライブラリの内部実装が外部に漏れる、変更の影響範囲が広がるといった問題につながります。

結論から言うと、publicは外部に公開するためのアクセス修飾子で、internalは同じアセンブリ内だけで使うためのアクセス修飾子です。

この記事では、C#のinternalpublicの違い、使い分け、実例、よくあるエラー、実務でのベストプラクティスまで詳しく解説します。

1. C#のinternalとpublicの違いを最初に理解する

C#のinternalpublicの違いを理解するうえで、最初に押さえるべきポイントは「アクセスできる範囲」です。

publicは外部のプロジェクトや別アセンブリからもアクセスできます。一方、internalは同じアセンブリ内からしかアクセスできません。

たとえば、あるクラスライブラリを別のアプリケーションから参照する場合、publicなクラスやメソッドは利用できますが、internalなクラスやメソッドは通常利用できません。

1-1. internalは「同じアセンブリ内」からアクセスできる

internalは、同じアセンブリ内からのみアクセスできるアクセス修飾子です。

C#
internal class UserValidator
{
internal bool IsValid(string name)
{
return !string.IsNullOrWhiteSpace(name);
}
}

このUserValidatorクラスは、同じアセンブリ内にあるコードからは利用できます。

C#
var validator = new UserValidator();
bool result = validator.IsValid("Taro");

しかし、別のアセンブリ、たとえば別プロジェクトからこのクラスを使おうとすると、通常はアクセスできません。

internalは「プロジェクト内部では使いたいが、外部には公開したくない」型やメンバーに向いています。

1-2. publicは「どこからでも」アクセスできる

publicは、アクセス制限が最も緩いアクセス修飾子です。

C#
public class UserService
{
public void CreateUser(string name)
{
Console.WriteLine($"{name}を作成しました");
}
}

publicなクラスやメソッドは、同じアセンブリ内だけでなく、そのアセンブリを参照している別プロジェクトからも利用できます。

たとえば、クラスライブラリにpublic class UserServiceを定義しておけば、別のコンソールアプリやWebアプリからそのクラスを呼び出せます。

publicは、ライブラリの利用者や他のプロジェクトに対して公開したいAPIに使います。

1-3. internalとpublicの違いを一覧表で比較

比較項目internalpublic
アクセスできる範囲同じアセンブリ内どこからでも
別プロジェクトからの利用原則できないできる
主な用途内部実装、補助クラス、プロジェクト内限定の処理外部公開API、ライブラリ利用者向けの型やメソッド
カプセル化しやすい公開範囲が広くなる
変更のしやすさ比較的変更しやすい外部利用者への影響を考慮する必要がある
実務での使いどころ内部ロジック、変換処理、バリデーション、リポジトリ実装などサービスクラス、DTO、公開メソッド、ライブラリの入口など

internalpublicの違いは、単に「アクセスできるかどうか」だけではありません。

publicにすると外部から使えるようになる一方で、その設計は外部利用者に対する約束になります。そのため、あとから名前や引数、戻り値を変更しにくくなります。

一方、internalであればアセンブリ内部に利用範囲を限定できるため、設計変更の影響範囲を抑えやすくなります。

1-4. まず覚えるべき結論:外部公開するかどうかで使い分ける

internalpublicの使い分けで迷ったら、次の基準で判断すると分かりやすいです。

判断基準選ぶアクセス修飾子
別プロジェクトや外部利用者に使わせたいpublic
同じプロジェクト内だけで使いたいinternal
ライブラリの利用者に公開するAPIであるpublic
実装の都合で作った補助クラスであるinternal
将来的に自由に変更したいinternalを優先
外部から直接呼ばれることを前提にするpublic

基本的には、外部に公開する必要があるものだけをpublicにし、それ以外はinternalprivateにしておくのが安全です。

2. C#のアクセス修飾子とは

C#のアクセス修飾子とは、クラス、メソッド、プロパティ、フィールドなどに対して「どこからアクセスできるか」を指定するためのキーワードです。

代表的なアクセス修飾子には、publicprivateprotectedinternalなどがあります。

アクセス修飾子を適切に使うことで、外部に見せるべきものと隠すべきものを分離できます。

2-1. アクセス修飾子の役割

アクセス修飾子の役割は、プログラムの部品に対するアクセス範囲を制御することです。

たとえば、次のようなクラスがあるとします。

C#
public class BankAccount
{
private decimal balance;

public void Deposit(decimal amount)
{
if (amount <= 0)
{
throw new ArgumentException("入金額は0より大きい必要があります");
}

balance += amount;
}

public decimal GetBalance()
{
return balance;
}
}

この例では、balanceフィールドはprivateなので、クラスの外部から直接変更できません。

C#
var account = new BankAccount();
account.Deposit(1000);

// account.balance = -9999; // 外部から直接変更できない

このように、アクセス修飾子を使うことで、不正な操作を防ぎ、クラスの状態を安全に保てます。

2-2. C#で使う主なアクセス修飾子の種類

C#でよく使うアクセス修飾子には、次のようなものがあります。

アクセス修飾子意味
publicどこからでもアクセス可能
private同じ型の内部からのみアクセス可能
protected同じ型または派生クラスからアクセス可能
internal同じアセンブリ内からアクセス可能
protected internal同じアセンブリ内、または別アセンブリの派生クラスからアクセス可能
private protected同じアセンブリ内の派生クラスからアクセス可能
file同じソースファイル内からアクセス可能

日常的によく使うのは、publicprivateprotectedinternalです。

特に、外部公開の可否を決める場面では、publicinternalの違いが重要になります。

2-3. public・private・protected・internalの違い

publicprivateprotectedinternalの違いを簡単に整理すると、次のようになります。

C#
public class Sample
{
public int PublicValue { get; set; }

private int PrivateValue { get; set; }

protected int ProtectedValue { get; set; }

internal int InternalValue { get; set; }
}

それぞれのアクセス範囲は次のとおりです。

修飾子アクセスできる場所
publicどこからでも
private同じクラス内のみ
protected同じクラスまたは派生クラス
internal同じアセンブリ内
protected internal同じアセンブリ内、または派生クラス
private protected同じアセンブリ内の派生クラス

publicは最も公開範囲が広く、privateは最も狭いアクセス修飾子です。

internalは、privateほど狭くはありませんが、publicほど広くもありません。同じアセンブリ内のコードからは使えるため、プロジェクト内部で共有したい処理に向いています。

2-4. アクセス修飾子を指定しない場合のデフォルト

C#では、アクセス修飾子を省略した場合にデフォルトのアクセス範囲が適用されます。

トップレベルに定義したクラスは、アクセス修飾子を省略するとinternalになります。

C#
class User
{
}

これは次のコードと同じ意味です。

C#
internal class User
{
}

一方、クラスのメンバーは、アクセス修飾子を省略すると基本的にprivateになります。

C#
public class User
{
string name;

void SetName(string value)
{
name = value;
}
}

これは次のように書いた場合とほぼ同じです。

C#
public class User
{
private string name;

private void SetName(string value)
{
name = value;
}
}

つまり、クラス自体は省略するとinternal、クラス内のメンバーは省略するとprivateです。

この仕様を知らないと、「クラスを別プロジェクトから参照できない」「メソッドが呼び出せない」といったエラーにつながります。

3. publicの意味と使い方

publicは、C#で最も公開範囲の広いアクセス修飾子です。

クラス、メソッド、プロパティ、フィールドなどにpublicを指定すると、同じプロジェクト内だけでなく、別プロジェクトや外部アセンブリからもアクセスできます。

3-1. publicでアクセスできる範囲

publicを指定した型やメンバーは、基本的にどこからでもアクセス可能です。

C#
public class Calculator
{
public int Add(int x, int y)
{
return x + y;
}
}

このCalculatorクラスがクラスライブラリに定義されている場合、別のプロジェクトからそのライブラリを参照すれば、次のように利用できます。

C#
var calculator = new Calculator();
int result = calculator.Add(10, 20);

publicは、外部のコードから直接使われることを前提にした型やメンバーに指定します。

3-2. publicクラスの基本例

publicクラスは、他のプロジェクトやアセンブリから利用できるクラスです。

C#
namespace MyLibrary
{
public class GreetingService
{
public string CreateMessage(string name)
{
return $"こんにちは、{name}さん";
}
}
}

別プロジェクトからこのクラスを利用する場合は、アセンブリ参照を追加し、名前空間を指定します。

C#
using MyLibrary;

var service = new GreetingService();
string message = service.CreateMessage("山田");

Console.WriteLine(message);

このように、publicクラスは外部から利用される入口になります。

3-3. publicメソッド・プロパティの基本例

クラスがpublicでも、メソッドやプロパティがpublicでなければ外部から呼び出せません。

C#
public class Product
{
public string Name { get; set; }

public int Price { get; set; }

public string GetDisplayText()
{
return $"{Name}: {Price}円";
}
}

この例では、NamePriceGetDisplayTextがすべてpublicなので、外部からアクセスできます。

C#
var product = new Product();
product.Name = "ノートPC";
product.Price = 120000;

Console.WriteLine(product.GetDisplayText());

一方、クラスがpublicでも、メンバーをprivateinternalにすると、アクセス範囲は制限されます。

C#
public class Product
{
public string Name { get; set; }

internal int CostPrice { get; set; }

private decimal DiscountRate { get; set; }
}

この場合、Nameは外部からアクセスできますが、CostPriceは同じアセンブリ内のみ、DiscountRateはクラス内部のみで利用できます。

3-4. publicを使うべきケース

publicを使うべきケースは、外部から利用されることを明確に意図している場合です。

たとえば、次のようなものはpublicにすることが多いです。

対象publicにする理由
ライブラリの利用者が使うクラス外部プロジェクトから生成・利用するため
Web APIのレスポンスDTOフレームワークや外部コードから参照されるため
外部に提供するサービスクラスアプリケーションの入口になるため
拡張メソッド呼び出し側から使える必要があるため
パッケージの公開API利用者に提供する機能だから

たとえば、NuGetパッケージとして提供する機能は、利用者が呼び出せるようにpublicにする必要があります。

C#
public class CsvExporter
{
public void Export(string filePath)
{
// CSV出力処理
}
}

このようなクラスは、外部のアプリケーションから呼び出されることを前提にしているため、publicが適しています。

3-5. publicを使いすぎるデメリット

publicは便利ですが、使いすぎると保守性が下がります。

publicにした型やメンバーは、外部から利用される可能性があります。そのため、あとから名前を変更したり、引数を変えたり、削除したりすると、利用側のコードに影響が出ます。

C#
public class OrderService
{
public void Calculate()
{
// 外部から使われているかもしれない
}
}

このCalculateメソッドが外部から使われている場合、メソッド名を変更すると呼び出し側でコンパイルエラーになります。

また、内部実装のためだけに作ったクラスをpublicにしてしまうと、本来触ってほしくない処理まで外部に公開されます。

C#
public class OrderCalculationHelper
{
public decimal CalculateInternalDiscount(decimal price)
{
return price * 0.1m;
}
}

このような補助クラスは、外部に公開する必要がないならinternalにする方が適切です。

C#
internal class OrderCalculationHelper
{
internal decimal CalculateInternalDiscount(decimal price)
{
return price * 0.1m;
}
}

publicは「誰でも使える」状態にするキーワードです。だからこそ、公開する価値があるものだけに限定して使うことが大切です。

4. internalの意味と使い方

internalは、同じアセンブリ内からのみアクセスできるアクセス修飾子です。

外部に公開したくないが、プロジェクト内の複数クラスから共有したい処理に向いています。

4-1. internalでアクセスできる範囲

internalを指定した型やメンバーは、同じアセンブリ内からアクセスできます。

C#
internal class TaxCalculator
{
internal decimal Calculate(decimal price)
{
return price * 1.1m;
}
}

同じアセンブリ内の別クラスからは、次のように呼び出せます。

C#
public class OrderService
{
public decimal GetTotalPrice(decimal price)
{
var calculator = new TaxCalculator();
return calculator.Calculate(price);
}
}

しかし、別アセンブリからはTaxCalculatorにアクセスできません。

このため、internalは「プロジェクト内部では共有したいが、外部には隠したい」クラスやメンバーに適しています。

4-2. 「同じアセンブリ内」とは何か

C#でいうアセンブリとは、コンパイルによって生成される.dll.exeの単位です。

Visual Studioや.NET CLIでプロジェクトをビルドすると、通常はプロジェクトごとに1つのアセンブリが生成されます。

たとえば、次のような構成があるとします。

MyApp.sln
├─ MyApp.Core
│ └─ MyApp.Core.dll
├─ MyApp.Web
│ └─ MyApp.Web.dll
└─ MyApp.Tests
└─ MyApp.Tests.dll

この場合、MyApp.CoreMyApp.WebMyApp.Testsはそれぞれ別アセンブリです。

MyApp.Core内で定義されたinternalクラスは、MyApp.Core内からは使えますが、MyApp.WebMyApp.Testsからは通常見えません。

つまり、internalの「同じアセンブリ内」とは、単に同じソリューション内という意味ではありません。

同じソリューションに複数プロジェクトがあっても、それぞれが別アセンブリなら、internalメンバーにはアクセスできません。

4-3. internalクラスの基本例

internalクラスは、同じアセンブリ内だけで使えるクラスです。

C#
namespace MyApp.Core
{
internal class PasswordHasher
{
internal string Hash(string password)
{
return $"hashed:{password}";
}
}
}

このPasswordHasherは、同じMyApp.Coreアセンブリ内からは利用できます。

C#
namespace MyApp.Core
{
public class UserService
{
public void Register(string password)
{
var hasher = new PasswordHasher();
string hashedPassword = hasher.Hash(password);

// ユーザー登録処理
}
}
}

一方、別プロジェクトからはPasswordHasherを直接利用できません。

外部からはUserServiceのような公開クラスを通して機能を使う設計にできます。

4-4. internalメソッド・プロパティの基本例

クラス自体をpublicにしつつ、一部のメソッドやプロパティだけをinternalにすることもできます。

C#
public class Invoice
{
public string InvoiceNumber { get; set; }

public decimal Amount { get; set; }

internal decimal InternalCost { get; set; }

internal void Recalculate()
{
// 内部計算処理
}
}

この場合、Invoiceクラスは外部から見えます。

しかし、InternalCostプロパティやRecalculateメソッドは同じアセンブリ内からしかアクセスできません。

外部プロジェクトから見えるのは、publicなメンバーだけです。

C#
var invoice = new Invoice();

invoice.InvoiceNumber = "INV-001";
invoice.Amount = 10000;

// invoice.InternalCost = 5000; // 別アセンブリからはアクセスできない
// invoice.Recalculate(); // 別アセンブリからはアクセスできない

このように、publicクラスの中にinternalメンバーを持たせることで、外部に見せる情報と内部だけで使う情報を分けられます。

4-5. internalを使うべきケース

internalを使うべきケースは、外部公開する必要がない内部実装です。

たとえば、次のようなものはinternalに向いています。

対象internalに向いている理由
補助クラス外部から直接使わせる必要がない
内部用の変換処理公開APIの一部ではない
バリデーターアプリ内部のルールとして使うため
Repositoryの実装クラスインターフェースだけ公開すればよい場合がある
設定読み込みクラス外部から直接操作させる必要がない
ドメイン内部の計算処理実装詳細として隠したい

例として、注文金額の計算を行う補助クラスを考えます。

C#
internal class OrderPriceCalculator
{
internal decimal CalculateTotal(decimal price, decimal taxRate)
{
return price + (price * taxRate);
}
}

このクラスは、外部利用者に直接使わせるというより、OrderServiceの内部で使う補助処理です。

C#
public class OrderService
{
public decimal CreateOrder(decimal price)
{
var calculator = new OrderPriceCalculator();
return calculator.CalculateTotal(price, 0.1m);
}
}

外部にはOrderServiceだけを公開し、OrderPriceCalculatorinternalで隠すことで、設計がシンプルになります。

4-6. internalが外部プロジェクトから見えない理由

internalが外部プロジェクトから見えないのは、アクセス範囲が同じアセンブリ内に限定されているからです。

たとえば、次のようなクラスライブラリがあるとします。

C#
namespace MyLibrary
{
internal class InternalLogger
{
internal void Log(string message)
{
Console.WriteLine(message);
}
}
}

別プロジェクトでMyLibraryを参照しても、InternalLoggerは利用できません。

C#
using MyLibrary;

// var logger = new InternalLogger(); // エラー

これは名前空間の問題ではなく、アクセス修飾子の問題です。

using MyLibrary;を書いても、アセンブリ参照を追加しても、internalな型は別アセンブリからは見えません。

外部から使いたい場合はpublicにする必要があります。

ただし、テストプロジェクトなど特定のアセンブリにだけinternalを見せたい場合は、InternalsVisibleToを使う方法があります。

5. internalとpublicの実例で違いを確認する

ここでは、internalpublicの違いを実際のコード例で確認します。

ポイントは、同じプロジェクト内ではinternalも使えるが、別プロジェクトからはpublicしか基本的に見えないということです。

5-1. 同じプロジェクト内から呼び出す例

まず、同じプロジェクト内に次の2つのクラスがあるとします。

C#
namespace SampleApp
{
internal class InternalMessageBuilder
{
internal string Build()
{
return "internalメッセージ";
}
}
}
C#
namespace SampleApp
{
public class MessageService
{
public string GetMessage()
{
var builder = new InternalMessageBuilder();
return builder.Build();
}
}
}

この2つのクラスが同じアセンブリ内にある場合、MessageServiceからInternalMessageBuilderを利用できます。

InternalMessageBuilderinternalですが、同じアセンブリ内なので問題ありません。

5-2. 別プロジェクトから参照する例

次に、SampleAppを別プロジェクトから参照するケースを考えます。

SampleApp側に次のクラスがあるとします。

C#
namespace SampleApp
{
public class PublicService
{
public string GetPublicMessage()
{
return "publicメッセージ";
}
}

internal class InternalService
{
internal string GetInternalMessage()
{
return "internalメッセージ";
}
}
}

別プロジェクトからは、PublicServiceは利用できます。

C#
using SampleApp;

var service = new PublicService();
Console.WriteLine(service.GetPublicMessage());

しかし、InternalServiceは利用できません。

C#
using SampleApp;

// var service = new InternalService(); // エラー

別プロジェクトから見えるのは、基本的にpublicな型です。

internalな型は、同じアセンブリ内に閉じた実装として扱われます。

5-3. publicクラスのinternalメンバーはどう見えるか

publicクラスの中にinternalメンバーを定義することもできます。

C#
namespace MyLibrary
{
public class Report
{
public string Title { get; set; }

internal string InternalCode { get; set; }

public void Print()
{
Console.WriteLine(Title);
}

internal void Validate()
{
// 内部検証処理
}
}
}

同じアセンブリ内では、すべてのメンバーにアクセスできます。

C#
var report = new Report();

report.Title = "売上レポート";
report.InternalCode = "R001";
report.Validate();
report.Print();

一方、別アセンブリからはpublicメンバーだけが見えます。

C#
var report = new Report();

report.Title = "売上レポート";
report.Print();

// report.InternalCode = "R001"; // エラー
// report.Validate(); // エラー

クラス自体がpublicでも、メンバーがinternalであれば外部からは呼び出せません。

5-4. internalクラスのpublicメンバーはどう見えるか

internalクラスの中にpublicメンバーを書くこともできます。

C#
internal class InternalReportBuilder
{
public string Build()
{
return "レポートを作成しました";
}
}

この場合、Buildメソッドはpublicですが、クラス自体がinternalです。

そのため、別アセンブリからはInternalReportBuilderクラス自体が見えません。

C#
// var builder = new InternalReportBuilder(); // 別アセンブリからはエラー
// builder.Build();

つまり、internalクラスの中にpublicメソッドを書いても、そのクラスが外部から見えない限り、外部プロジェクトからは直接呼び出せません。

このpublicは、あくまで「その型にアクセスできる範囲の中ではpublic」という意味になります。

同じアセンブリ内では次のように呼び出せます。

C#
var builder = new InternalReportBuilder();
string result = builder.Build();

5-5. コンパイルエラーになるパターンと原因

internalpublicでよくあるコンパイルエラーには、次のようなものがあります。

まず、別アセンブリからinternalクラスを呼び出そうとするケースです。

C#
// 別プロジェクト側
var service = new InternalService();

この場合、InternalServiceinternalなら、アクセスできないためコンパイルエラーになります。

次に、publicメソッドの戻り値や引数にinternal型を使ってしまうケースです。

C#
internal class InternalResult
{
}

public class ResultService
{
public InternalResult GetResult()
{
return new InternalResult();
}
}

このコードは、公開範囲の整合性に問題があります。

GetResultメソッドはpublicなので外部から呼び出せるはずですが、戻り値のInternalResultは外部から見えません。

そのため、「publicメソッドなのに戻り値の型が外部から見えない」という矛盾が発生します。

この場合は、戻り値の型をpublicにするか、メソッド自体をinternalにします。

C#
public class PublicResult
{
}

public class ResultService
{
public PublicResult GetResult()
{
return new PublicResult();
}
}

または次のようにします。

C#
internal class InternalResult
{
}

public class ResultService
{
internal InternalResult GetResult()
{
return new InternalResult();
}
}

アクセス修飾子を設計するときは、クラス、メソッド、引数、戻り値の公開範囲に矛盾がないか確認することが重要です。

6. internalとpublicの使い分け方

internalpublicの使い分けは、C#の設計において非常に重要です。

判断基準はシンプルで、外部に公開する必要があるならpublic、プロジェクト内部だけで使うならinternalです。

6-1. 外部ライブラリとして公開するならpublic

クラスライブラリやNuGetパッケージとして外部に機能を提供する場合、利用者が直接使う型やメソッドはpublicにします。

C#
namespace MyCsvLibrary
{
public class CsvReader
{
public IEnumerable<string[]> Read(string filePath)
{
// CSV読み込み処理
return new List<string[]>();
}
}
}

このCsvReaderは、ライブラリ利用者が直接呼び出す想定なのでpublicが適切です。

外部公開するクラスは、使い方が分かりやすく、変更に強い設計にする必要があります。

C#
var reader = new CsvReader();
var rows = reader.Read("sample.csv");

このように外部から呼び出される入口はpublicにします。

6-2. プロジェクト内部だけで使うならinternal

外部から直接使わせる必要がないクラスは、internalにするのが基本です。

C#
internal class CsvLineParser
{
internal string[] Parse(string line)
{
return line.Split(',');
}
}

CsvLineParserは、CsvReaderの内部で使われる補助クラスです。

C#
public class CsvReader
{
public IEnumerable<string[]> Read(string filePath)
{
var parser = new CsvLineParser();

foreach (var line in File.ReadLines(filePath))
{
yield return parser.Parse(line);
}
}
}

外部利用者にはCsvReaderだけを公開し、CsvLineParserは隠しておくことで、ライブラリの使い方がシンプルになります。

また、内部実装を変更しやすくなります。

6-3. API設計でpublicを最小限にする考え方

API設計では、publicを最小限にすることが重要です。

publicにしたものは外部から利用される可能性があり、一度公開すると簡単には変更できません。

たとえば、次のように何でもpublicにしてしまうと、外部から内部処理まで呼び出せてしまいます。

C#
public class PaymentService
{
public void ValidateCard()
{
}

public void ConnectToPaymentGateway()
{
}

public void SavePaymentLog()
{
}

public void Pay()
{
}
}

この場合、利用者に本当に使ってほしいのはPayだけかもしれません。

その場合は、内部処理をprivateinternalに分けます。

C#
public class PaymentService
{
public void Pay()
{
ValidateCard();
ConnectToPaymentGateway();
SavePaymentLog();
}

private void ValidateCard()
{
}

private void ConnectToPaymentGateway()
{
}

private void SavePaymentLog()
{
}
}

別クラスからも共有する内部処理であれば、internalクラスに切り出す方法もあります。

C#
internal class PaymentLogWriter
{
internal void Save()
{
// ログ保存処理
}
}

公開APIはできるだけ少なくし、内部実装は隠すのが保守しやすい設計です。

6-4. チーム開発でinternalを使うメリット

チーム開発では、internalを使うことで「このクラスはプロジェクト内部用である」という意図をコード上で表現できます。

たとえば、チームメンバーが次のようなクラスを見た場合、外部向けのAPIではないことが分かります。

C#
internal class UserNameNormalizer
{
internal string Normalize(string name)
{
return name.Trim().ToLowerInvariant();
}
}

internalにしておくことで、外部プロジェクトから誤って使われることを防げます。

また、実装の変更も比較的しやすくなります。

C#
internal class UserNameNormalizer
{
internal string Normalize(string name)
{
return name.Trim();
}
}

内部利用だけであれば、影響範囲を同じアセンブリ内に限定できます。

チーム開発では、publicinternalを明確に使い分けることで、設計意図が伝わりやすくなります。

6-5. 保守性・影響範囲・カプセル化で判断する

internalpublicを使い分けるときは、次の3つの観点で考えると判断しやすくなります。

観点publicinternal
保守性公開後は変更に注意が必要内部利用なので変更しやすい
影響範囲外部プロジェクトにも影響する同じアセンブリ内に限定される
カプセル化公開範囲が広い実装詳細を隠しやすい

外部に公開する必要がないなら、まずinternalを検討します。

特に、ライブラリや大規模アプリケーションでは、publicを増やしすぎないことが大切です。

publicは外部との契約、internalは内部実装、と考えると使い分けが明確になります。

7. internalとpublicでよくある疑問

ここでは、C#のinternalpublicに関してよくある疑問を整理します。

特に、privateprotectedとの違い、クラスとメンバーの組み合わせについては混乱しやすいポイントです。

7-1. internalはprivateと何が違うのか

internalprivateは、どちらも外部に公開しないために使われますが、アクセスできる範囲が異なります。

privateは同じクラスの中からしかアクセスできません。

C#
public class Sample
{
private int value;

private void SetValue(int value)
{
this.value = value;
}
}

一方、internalは同じアセンブリ内の別クラスからもアクセスできます。

C#
internal class SharedHelper
{
internal void Execute()
{
Console.WriteLine("内部処理");
}
}

public class SampleService
{
public void Run()
{
var helper = new SharedHelper();
helper.Execute();
}
}

違いをまとめると、次のようになります。

修飾子アクセスできる範囲
private同じクラス内のみ
internal同じアセンブリ内

クラスの中だけで使うならprivate、同じアセンブリ内の複数クラスで共有するならinternalが向いています。

7-2. internalはprotectedと何が違うのか

internalprotectedもアクセス範囲が異なります。

protectedは、同じクラスまたは派生クラスからアクセスできます。

C#
public class BaseService
{
protected void WriteLog()
{
Console.WriteLine("ログ出力");
}
}

public class UserService : BaseService
{
public void CreateUser()
{
WriteLog();
}
}

一方、internalは継承関係に関係なく、同じアセンブリ内であればアクセスできます。

C#
internal class LogWriter
{
internal void Write()
{
Console.WriteLine("ログ出力");
}
}

違いをまとめると、次のようになります。

修飾子判断基準
protected継承関係があるか
internal同じアセンブリ内か

派生クラスに使わせたいならprotected、同じプロジェクト内で共有したいならinternalを使います。

7-3. internalクラスの中にpublicメソッドを書けるのか

internalクラスの中にpublicメソッドを書くことはできます。

C#
internal class InternalTool
{
public void Execute()
{
Console.WriteLine("実行");
}
}

ただし、クラス自体がinternalなので、別アセンブリからはInternalToolにアクセスできません。

そのため、このpublicメソッドは「InternalToolにアクセスできる範囲内ではpublic」という意味になります。

同じアセンブリ内では呼び出せます。

C#
var tool = new InternalTool();
tool.Execute();

別アセンブリからは、クラス自体が見えないため呼び出せません。

C#
// var tool = new InternalTool(); // エラー
// tool.Execute();

つまり、internalクラス内のpublicメソッドは、外部公開APIになるわけではありません。

7-4. publicクラスの中にinternalメソッドを書けるのか

publicクラスの中にinternalメソッドを書くこともできます。

C#
public class PublicTool
{
public void Execute()
{
Console.WriteLine("外部から呼べる処理");
}

internal void ExecuteInternal()
{
Console.WriteLine("内部専用処理");
}
}

この場合、別アセンブリからはPublicToolクラスとExecuteメソッドにはアクセスできます。

しかし、ExecuteInternalメソッドにはアクセスできません。

C#
var tool = new PublicTool();

tool.Execute();

// tool.ExecuteInternal(); // 別アセンブリからはエラー

この使い方は実務でもよくあります。

たとえば、外部に見せるメソッドはpublicにし、同じアセンブリ内の処理からだけ呼びたい補助メソッドはinternalにします。

7-5. internalは実務で使われるのか

internalは実務でよく使われます。

特に、次のような場面で便利です。

場面internalを使う理由
クラスライブラリ開発外部に見せるAPIを制限するため
Webアプリ開発アプリ内部の実装を整理するため
テスト対象の内部ロジックInternalsVisibleToと組み合わせられるため
大規模開発影響範囲をアセンブリ内に限定するため
ドメイン設計外部公開モデルと内部実装を分けるため

たとえば、ASP.NET Coreアプリケーションでも、外部公開するコントローラーやDTOはpublicにし、内部で使うサービス実装や補助クラスをinternalにすることがあります。

C#
public interface IUserService
{
void CreateUser(string name);
}

internal class UserService : IUserService
{
public void CreateUser(string name)
{
// ユーザー作成処理
}
}

このように、インターフェースだけをpublicにし、実装クラスをinternalにする設計もよく使われます。

8. internalを別アセンブリから使う方法

通常、internalは同じアセンブリ内からしかアクセスできません。

しかし、特定の別アセンブリに対してだけinternalメンバーへのアクセスを許可する方法があります。

それがInternalsVisibleToです。

8-1. InternalsVisibleToとは

InternalsVisibleToは、指定した別アセンブリからinternalメンバーにアクセスできるようにするための属性です。

主に、テストプロジェクトから本体プロジェクトのinternalクラスやメソッドをテストしたい場合に使われます。

C#
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("MyApp.Tests")]

この記述を、本体プロジェクト側の.csファイルやAssemblyInfo.csに追加します。

これにより、MyApp.Testsアセンブリから、本体プロジェクトのinternalクラスやメンバーにアクセスできるようになります。

8-2. テストプロジェクトからinternalメンバーを参照する例

たとえば、本体プロジェクトMyApp.Coreに次のinternalクラスがあるとします。

C#
namespace MyApp.Core
{
internal class PriceCalculator
{
internal decimal CalculateTaxIncludedPrice(decimal price)
{
return price * 1.1m;
}
}
}

通常、テストプロジェクトMyApp.Testsからは、このPriceCalculatorにアクセスできません。

そこで、本体プロジェクト側に次の属性を追加します。

C#
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("MyApp.Tests")]

すると、テストプロジェクトから次のようにテストできます。

C#
using MyApp.Core;
using Xunit;

public class PriceCalculatorTests
{
[Fact]
public void CalculateTaxIncludedPrice_ReturnsTaxIncludedPrice()
{
var calculator = new PriceCalculator();

decimal result = calculator.CalculateTaxIncludedPrice(1000);

Assert.Equal(1100, result);
}
}

これにより、本番コードではinternalとして隠しつつ、テストコードからは検証できるようになります。

8-3. InternalsVisibleToを使うときの注意点

InternalsVisibleToは便利ですが、使いすぎには注意が必要です。

本来internalは、アセンブリ内部に実装を閉じ込めるための仕組みです。

InternalsVisibleToで多くのアセンブリに公開してしまうと、internalによる隠蔽の意味が弱くなります。

C#
[assembly: InternalsVisibleTo("MyApp.Tests")]
[assembly: InternalsVisibleTo("MyApp.Tools")]
[assembly: InternalsVisibleTo("MyApp.Admin")]

このように多くのアセンブリに内部アクセスを許可すると、内部実装を変更したときの影響範囲が広がります。

また、厳密名付きアセンブリを使っている場合は、公開先アセンブリの公開キーを指定する必要がある場合があります。

InternalsVisibleToは、主にテスト用途や明確な理由がある場合に限定して使うのが望ましいです。

8-4. internalを安易に公開しないための判断基準

internalを別アセンブリから使いたくなった場合は、すぐにpublicへ変更するのではなく、次の点を確認します。

確認ポイント判断
外部利用者に正式に提供する機能かそうならpublicを検討
テストのためだけに必要かInternalsVisibleToを検討
内部実装に依存しすぎていないかテスト設計を見直す
今後も安定して公開できるか不安ならinternalのままにする
代わりにpublic API経由で検証できないかできるならpublic APIを使う

internalは、内部実装を守るための重要な仕組みです。

一時的に便利だからという理由だけでpublicにすると、後から変更しにくくなります。

外部公開するかどうかは、長期的な保守性を考えて判断しましょう。

9. C#のinternalとpublicで起きやすいエラーと対処法

internalpublicの違いを理解していないと、C#ではアクセス修飾子に関するエラーが発生しやすくなります。

ここでは、よくあるエラーと対処法を紹介します。

9-1. 「アクセスできない保護レベルです」エラーの原因

C#でよく見るエラーに、「アクセスできない保護レベルです」というものがあります。

これは、アクセスしようとしているクラス、メソッド、プロパティなどが、現在のコードからアクセスできない修飾子になっている場合に発生します。

たとえば、別アセンブリからinternalクラスを使おうとするとエラーになります。

C#
internal class InternalService
{
internal void Execute()
{
}
}

別プロジェクト側で次のように書くと、アクセスできません。

C#
var service = new InternalService(); // エラー

対処法は、外部から使う必要があるならpublicに変更することです。

C#
public class InternalService
{
public void Execute()
{
}
}

ただし、本当に外部公開すべきかは慎重に判断します。

内部実装であれば、publicにせず、公開済みのクラス経由で呼び出す設計にする方がよい場合があります。

9-2. 別プロジェクトからinternalクラスが見えない場合

別プロジェクトからクラスが見えない場合、原因は次のどれかであることが多いです。

原因確認内容
クラスがinternalになっている外部から使うならpublicにする
アクセス修飾子を省略しているトップレベルクラスは省略するとinternal
プロジェクト参照がない参照設定を追加する
名前空間が違うusingまたは完全修飾名を確認する
ビルドが古いクリーンビルドする

特に多いのは、クラスにpublicを書き忘れているケースです。

C#
class UserService
{
}

このクラスは、アクセス修飾子を省略しているためinternal扱いです。

別プロジェクトから使いたい場合は、次のようにpublicを付けます。

C#
public class UserService
{
}

同じソリューション内にあるからといって、自動的にinternalクラスへアクセスできるわけではありません。

プロジェクトが違えば、通常は別アセンブリです。

9-3. クラスは見えるのにメソッドが呼べない場合

クラス自体は見えるのに、メソッドやプロパティが呼べない場合もあります。

たとえば、次のようなクラスです。

C#
public class UserService
{
void CreateUser()
{
}
}

UserServiceクラスはpublicなので外部から見えます。

しかし、CreateUserメソッドにはアクセス修飾子がありません。

クラス内のメソッドは、アクセス修飾子を省略すると基本的にprivateになります。

そのため、外部からは呼び出せません。

C#
var service = new UserService();

// service.CreateUser(); // エラー

外部から呼び出したい場合は、メソッドにもpublicを付ける必要があります。

C#
public class UserService
{
public void CreateUser()
{
}
}

クラスだけでなく、メソッドやプロパティのアクセス修飾子も確認しましょう。

9-4. 参照設定・名前空間・アクセス修飾子の確認手順

internalpublicに関するエラーが出た場合は、次の順番で確認すると原因を見つけやすいです。

手順確認内容
1利用したいクラスがpublicか確認する
2利用したいメソッドやプロパティがpublicか確認する
3別プロジェクトから使う場合、プロジェクト参照があるか確認する
4usingで正しい名前空間を指定しているか確認する
5クラス名やメソッド名が正しいか確認する
6ビルドし直して最新状態になっているか確認する
7internalのまま使いたい場合、InternalsVisibleToが必要か検討する

たとえば、次のようなコードでは、クラスもメソッドも外部から使えません。

C#
class OrderService
{
void CreateOrder()
{
}
}

別プロジェクトから使いたいなら、次のようにします。

C#
public class OrderService
{
public void CreateOrder()
{
}
}

ただし、外部公開が不要なら、無理にpublicへ変更する必要はありません。

本当に公開すべき型やメンバーだけをpublicにすることが重要です。

10. internalとpublicのベストプラクティス

internalpublicを適切に使い分けることで、C#のコードは保守しやすくなります。

ここでは、実務で意識したいベストプラクティスを紹介します。

10-1. 迷ったらpublicではなくinternalを検討する

アクセス修飾子で迷ったときは、まずpublicではなくinternalを検討します。

外部から使う必要がないものをpublicにすると、公開範囲が広がりすぎます。

C#
internal class FilePathBuilder
{
internal string Build(string directory, string fileName)
{
return Path.Combine(directory, fileName);
}
}

このような補助クラスは、外部公開する必要がなければinternalで十分です。

publicは、外部に提供する明確な理由がある場合に使います。

C#
public class FileExportService
{
public void Export(string filePath)
{
// 外部から呼び出す入口
}
}

内部実装はinternal、外部の入口はpublicと分けることで、設計が分かりやすくなります。

10-2. 外部に公開する型と内部実装を分ける

実務では、外部公開用の型と内部実装用の型を分ける設計が有効です。

C#
public class UserRegistrationService
{
public void Register(string name, string password)
{
var validator = new UserValidator();
var hasher = new PasswordHasher();

validator.Validate(name, password);
string hashedPassword = hasher.Hash(password);

// 登録処理
}
}
C#
internal class UserValidator
{
internal void Validate(string name, string password)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("名前は必須です");
}

if (password.Length < 8)
{
throw new ArgumentException("パスワードは8文字以上必要です");
}
}
}
C#
internal class PasswordHasher
{
internal string Hash(string password)
{
return $"hashed:{password}";
}
}

この設計では、外部から使うのはUserRegistrationServiceだけです。

UserValidatorPasswordHasherは内部実装なので、外部から直接使わせる必要がありません。

10-3. public APIは変更しにくい前提で設計する

publicにしたAPIは、外部から使われる可能性があります。

そのため、あとから変更しにくい前提で慎重に設計する必要があります。

C#
public class CustomerService
{
public Customer GetCustomer(int id)
{
// 顧客取得処理
return new Customer();
}
}

このGetCustomerメソッドを外部利用者が使っている場合、次のような変更は影響が大きくなります。

C#
public Customer GetCustomer(string id)
{
return new Customer();
}

引数の型をintからstringに変えると、既存の呼び出しコードが壊れる可能性があります。

public APIを設計するときは、次の点を意識します。

観点確認内容
メソッド名役割が明確か
引数将来変更しにくくないか
戻り値外部に公開してよい型か
例外利用者が扱いやすいか
責務1つのAPIに処理を詰め込みすぎていないか

publicは外部との契約です。内部実装とは違い、簡単に変えられない前提で設計しましょう。

10-4. internalで実装詳細を隠して保守性を高める

internalを使うと、実装詳細をアセンブリ内部に隠せます。

これにより、外部利用者に影響を与えずに内部処理を変更しやすくなります。

たとえば、最初は次のような実装だったとします。

C#
internal class DiscountCalculator
{
internal decimal Calculate(decimal price)
{
return price * 0.9m;
}
}

後から割引ロジックを変更しても、このクラスが外部公開されていなければ、影響範囲はアセンブリ内に限定されます。

C#
internal class DiscountCalculator
{
internal decimal Calculate(decimal price)
{
if (price >= 10000)
{
return price * 0.85m;
}

return price * 0.95m;
}
}

外部には、次のようなpublicなサービスだけを見せます。

C#
public class OrderService
{
public decimal CalculateTotal(decimal price)
{
var calculator = new DiscountCalculator();
return calculator.Calculate(price);
}
}

このように、外部から見えるAPIを安定させ、内部実装をinternalで隠すことで、保守性を高められます。

10-5. 命名・コメント・設計ルールをチームで統一する

internalpublicの使い方は、チーム内でルールを統一しておくと効果的です。

たとえば、次のようなルールを決めておくと、設計のばらつきを減らせます。

ルール例目的
外部から使う型だけpublicにする公開APIを増やしすぎない
補助クラスは原則internalにする内部実装を隠す
クラスメンバーは必要なものだけpublicにするカプセル化を守る
public APIにはXMLコメントを書く利用者に意図を伝える
internalの命名も分かりやすくするチーム内の可読性を高める

たとえば、公開APIには次のようにコメントを書くと、利用者に意図が伝わりやすくなります。

C#
/// <summary>
/// ユーザーを登録するサービスです。
/// </summary>
public class UserRegistrationService
{
/// <summary>
/// 指定された名前とメールアドレスでユーザーを登録します。
/// </summary>
public void Register(string name, string email)
{
// 登録処理
}
}

一方、internalなクラスにも、複雑な処理がある場合はコメントを残すと保守しやすくなります。

C#
internal class EmailAddressNormalizer
{
internal string Normalize(string email)
{
return email.Trim().ToLowerInvariant();
}
}

アクセス修飾子は、単なる文法ではなく設計意図を表すものです。

チームでルールを統一すると、コードの読みやすさと保守性が向上します。

まとめ

C#のinternalpublicの違いは、アクセスできる範囲にあります。

publicはどこからでもアクセスできるアクセス修飾子で、外部プロジェクトやライブラリ利用者に公開する型やメンバーに使います。

一方、internalは同じアセンブリ内からのみアクセスできるアクセス修飾子で、プロジェクト内部だけで使うクラスやメソッドに適しています。

使い分けの基本は、外部に公開する必要があるならpublic、内部実装として隠したいならinternalです。

特にライブラリや大規模なC#アプリケーションでは、publicを増やしすぎると変更の影響範囲が広がります。公開APIは外部との契約になるため、慎重に設計する必要があります。

一方、internalを活用すれば、内部実装を隠し、アセンブリ内に影響範囲を限定できます。これにより、保守性やカプセル化を高められます。

また、internalメンバーをテストプロジェクトから参照したい場合は、InternalsVisibleToを使うことで、特定のアセンブリにだけ内部アクセスを許可できます。

C#でアクセス修飾子を設計するときは、次の考え方を意識すると分かりやすくなります。

判断基準選ぶ修飾子
外部プロジェクトから使わせたいpublic
同じアセンブリ内だけで使いたいinternal
同じクラス内だけで使いたいprivate
派生クラスから使わせたいprotected

publicは外部公開するためのもの、internalは内部実装を隠すためのものです。

この違いを理解して使い分けることで、C#のコードはより安全で保守しやすくなります。