C# internalとは?使い方・publicとの違い・アクセスできない原因をわかりやすく解説

はじめに

C#のinternalは、クラスやメソッドなどのアクセス範囲を「同じアセンブリ内」に限定するアクセス修飾子です。

C#では、publicprivateprotectedinternalなどを使って、クラスやメンバーをどこから利用できるかを制御します。その中でもinternalは、ライブラリ開発、アプリケーション内部の実装整理、単体テストとの連携などでよく使われます。

一方で、internalは「同じプロジェクトなら使える」「別プロジェクトからは基本的に使えない」「namespaceとは関係ない」といった特徴があるため、C#を学び始めたばかりの人が混乱しやすい修飾子でもあります。

この記事では、C#のinternalとは何か、基本的な使い方、publicとの違い、アクセスできない原因、InternalsVisibleToを使ってテストコードからアクセスする方法まで、わかりやすく解説します。

1. C#のinternalとは?

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

C#のinternalは、型やメンバーを「同じアセンブリ内のコードからのみアクセス可能」にするアクセス修飾子です。Microsoft公式ドキュメントでも、internalは型および型メンバーに使うアクセス修飾子と説明されています。

ここで重要なのは、「同じクラス内」ではなく「同じアセンブリ内」という点です。

アセンブリとは、C#のプロジェクトをビルドした結果として生成される.dll.exeの単位と考えるとわかりやすいです。通常、1つのC#プロジェクトは1つのアセンブリとしてビルドされます。

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

MyApp.Core        → MyApp.Core.dll
MyApp.Web → MyApp.Web.dll
MyApp.Tests → MyApp.Tests.dll

この場合、MyApp.Coreプロジェクト内でinternalとして定義したクラスやメソッドは、同じMyApp.Core.dll内からはアクセスできます。しかし、別アセンブリであるMyApp.Web.dllMyApp.Tests.dllからは、通常はアクセスできません。

つまり、internalは「プロジェクト内部では使いたいが、外部には公開したくないもの」を表現するための修飾子です。

1-2. internalを使える対象:クラス・メソッド・プロパティ・フィールドなど

internalは、さまざまな要素に指定できます。

代表的には、次のようなものに使えます。

対象
クラスinternal class UserService
インターフェイスinternal interface IUserRepository
構造体internal struct Money
列挙型internal enum UserStatus
メソッドinternal void Save()
プロパティinternal string Name { get; set; }
フィールドinternal int count;
コンストラクターinternal User()

たとえば、クラス自体をinternalにすると、そのクラスは同じアセンブリ内からしか利用できません。

C#
internal class UserService
{
public void Register()
{
Console.WriteLine("ユーザーを登録しました。");
}
}

この例では、UserServiceクラスはinternalです。そのため、同じアセンブリ内のコードからは使えますが、別のアセンブリからは直接使えません。

一方で、クラスはpublicにして、特定のメソッドだけをinternalにすることもできます。

C#
public class UserService
{
public void Register()
{
Validate();
Console.WriteLine("ユーザーを登録しました。");
}

internal void Validate()
{
Console.WriteLine("入力値を検証しました。");
}
}

この場合、Registerメソッドは外部から使えますが、Validateメソッドは同じアセンブリ内からしか使えません。

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

C#では、アクセス修飾子を省略した場合のデフォルトが、定義する場所によって変わります。

namespace直下にクラスを定義した場合、アクセス修飾子を省略すると基本的にinternalになります。

C#
class UserService
{
}

上のコードは、次のように書いた場合とほぼ同じ意味です。

C#
internal class UserService
{
}

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

C#
public class UserService
{
void Validate()
{
Console.WriteLine("検証処理");
}
}

このValidateメソッドは、次のようにprivateを付けた場合と同じです。

C#
public class UserService
{
private void Validate()
{
Console.WriteLine("検証処理");
}
}

つまり、「省略時は常にinternal」ではありません。

namespace直下のクラスなどはinternal、クラス内のメソッドやフィールドなどはprivateになる、と覚えておくとよいでしょう。

1-4. internalが必要になる代表的な場面

internalが必要になる代表的な場面は、外部に公開したくない実装を隠したいときです。

たとえば、ライブラリを作っている場合、利用者に使ってほしいクラスやメソッドはpublicにします。しかし、ライブラリ内部でだけ使う補助クラスまでpublicにしてしまうと、外部のコードから自由に使えてしまいます。

C#
public class CsvReader
{
public void Read(string path)
{
var parser = new CsvParser();
parser.Parse(path);
}
}

internal class CsvParser
{
public void Parse(string path)
{
Console.WriteLine("CSVを解析します。");
}
}

この例では、ライブラリ利用者にはCsvReaderだけを使ってほしいとします。CsvParserは内部処理用のクラスなので、internalにして外部から直接使えないようにしています。

internalは、次のような場面で役立ちます。

場面internalを使う理由
ライブラリ内部の補助クラス外部APIとして公開したくない
アプリケーション内部の共通処理同じプロジェクト内だけで使いたい
外部から呼ばれると困るメソッド誤用を防ぎたい
テスト対象にしたい内部ロジックInternalsVisibleToでテスト可能にできる
APIの公開範囲を整理したい利用者に見せる機能を絞れる

2. internalの基本的な使い方

2-1. internalクラスの定義例

internalクラスは、次のように定義します。

C#
internal class OrderCalculator
{
public int CalculateTotal(int price, int quantity)
{
return price * quantity;
}
}

このOrderCalculatorクラスは、同じアセンブリ内であれば利用できます。

C#
public class OrderService
{
public void CreateOrder()
{
var calculator = new OrderCalculator();
int total = calculator.CalculateTotal(1000, 3);

Console.WriteLine($"合計金額: {total}円");
}
}

OrderServiceOrderCalculatorが同じプロジェクト、つまり同じアセンブリ内にある場合、このコードは問題なく動作します。

internalクラスの中にpublicメソッドを定義している点にも注目してください。

C#
internal class OrderCalculator
{
public int CalculateTotal(int price, int quantity)
{
return price * quantity;
}
}

クラス自体がinternalなので、メソッドがpublicであっても、そのクラスを使える範囲は同じアセンブリ内に限定されます。

つまり、外部アセンブリから見れば、OrderCalculatorクラス自体にアクセスできないため、その中のpublicメソッドにもアクセスできません。

2-2. internalメソッド・プロパティの定義例

クラス全体ではなく、メソッドやプロパティだけをinternalにすることもできます。

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

internal string NormalizedName { get; set; }

internal void Normalize()
{
NormalizedName = Name.Trim().ToLower();
}
}

この例では、UserクラスとNameプロパティはpublicなので外部からアクセスできます。

一方、NormalizedNameプロパティとNormalizeメソッドはinternalなので、同じアセンブリ内からしかアクセスできません。

このようにすると、外部の利用者にはシンプルなAPIだけを見せつつ、内部処理用のメンバーを隠すことができます。

C#
public class UserService
{
public void Save(User user)
{
user.Normalize();

Console.WriteLine($"保存対象: {user.NormalizedName}");
}
}

UserServiceが同じアセンブリ内にある場合、internalメンバーであるNormalizeNormalizedNameにアクセスできます。

2-3. 同じプロジェクト内からinternalメンバーにアクセスする例

次のような同じプロジェクト内の構成を考えます。

MyApp
├── Services
│ └── UserService.cs
└── Utilities
└── PasswordHasher.cs

PasswordHasher.csを次のように定義します。

C#
namespace MyApp.Utilities;

internal class PasswordHasher
{
public string Hash(string password)
{
return $"hashed_{password}";
}
}

同じプロジェクト内のUserService.csからは、次のようにアクセスできます。

C#
using MyApp.Utilities;

namespace MyApp.Services;

public class UserService
{
public void Register(string password)
{
var hasher = new PasswordHasher();
string hashedPassword = hasher.Hash(password);

Console.WriteLine(hashedPassword);
}
}

PasswordHasherinternalですが、UserServiceと同じアセンブリ内にあるため利用できます。

ここで大切なのは、フォルダが違っていても、namespaceが違っていても、同じアセンブリ内であればinternalにアクセスできるという点です。

internalのアクセス範囲を決めるのは、フォルダ構成やnamespaceではありません。あくまでアセンブリです。

2-4. 別プロジェクトからinternalにアクセスできない例

次に、別プロジェクトからinternalクラスにアクセスしようとする例を見てみます。

MyApp.Core
└── PasswordHasher.cs

MyApp.Web
└── LoginController.cs

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

C#
namespace MyApp.Core;

internal class PasswordHasher
{
public string Hash(string password)
{
return $"hashed_{password}";
}
}

別プロジェクトのMyApp.Webからアクセスしようとすると、コンパイルエラーになります。

C#
using MyApp.Core;

namespace MyApp.Web;

public class LoginController
{
public void Login(string password)
{
var hasher = new PasswordHasher(); // エラー
string hash = hasher.Hash(password);
}
}

エラー内容は環境によって異なりますが、代表的には次のようなメッセージになります。

'PasswordHasher' is inaccessible due to its protection level

これは、PasswordHasherinternalであり、MyApp.Webが別アセンブリだからです。

プロジェクト参照を追加していても、internalのアクセス制限は解除されません。別プロジェクトから使いたい場合は、publicにするか、後述するInternalsVisibleToを使う必要があります。

3. internalとpublicの違い

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

publicは、最も広いアクセス範囲を持つアクセス修飾子です。

publicにした型やメンバーは、参照可能な場所であれば、別アセンブリからでもアクセスできます。Microsoft公式ドキュメントでも、publicはアクセスが制限されないアクセシビリティとして説明されています。

C#
public class ProductService
{
public void GetProducts()
{
Console.WriteLine("商品一覧を取得します。");
}
}

このProductServiceクラスはpublicなので、別プロジェクトから参照して使うことができます。

C#
var service = new ProductService();
service.GetProducts();

ライブラリやAPIとして外部に提供したいクラス、メソッド、プロパティにはpublicを使います。

3-2. internalは同じアセンブリ内にアクセス範囲を制限する

internalは、publicよりも狭いアクセス範囲です。

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

このProductValidatorは、同じアセンブリ内からは使えますが、別アセンブリからは使えません。

たとえば、ProductServiceの内部でだけ使う検証クラスであれば、internalにすることで外部からの直接利用を防げます。

C#
public class ProductService
{
public void Create(string name)
{
var validator = new ProductValidator();

if (!validator.IsValid(name))
{
throw new ArgumentException("商品名が不正です。");
}

Console.WriteLine("商品を作成しました。");
}
}

外部にはProductServiceだけを公開し、内部のProductValidatorは隠す。このような設計にinternalは向いています。

3-3. publicとinternalの使い分け基準

publicinternalの使い分けは、「外部の利用者に使わせたいかどうか」で考えるとわかりやすいです。

判断基準選ぶ修飾子
外部プロジェクトから使わせたいpublic
同じプロジェクト内だけで使いたいinternal
ライブラリ利用者に見せたいAPIpublic
ライブラリ内部だけの実装internal
将来的にも外部に公開したくないinternal
外部に公開しても仕様として維持できるpublic

publicにすると、外部コードから利用される可能性があります。そのため、後から名前を変えたり、引数を変更したり、削除したりすると、利用者のコードに影響を与えます。

一方、internalであれば利用範囲が同じアセンブリ内に限定されるため、内部実装として比較的変更しやすくなります。

つまり、publicは「外部との契約」、internalは「アセンブリ内部の実装」と考えると整理しやすいです。

3-4. ライブラリ開発でinternalを使うメリット

ライブラリ開発では、internalを適切に使うことで、公開APIを整理できます。

たとえば、次のようなライブラリを考えます。

C#
public class JsonLoader
{
public string Load(string path)
{
var fileReader = new InternalFileReader();
var validator = new JsonValidator();

string json = fileReader.Read(path);
validator.Validate(json);

return json;
}
}

internal class InternalFileReader
{
public string Read(string path)
{
return File.ReadAllText(path);
}
}

internal class JsonValidator
{
public void Validate(string json)
{
if (string.IsNullOrWhiteSpace(json))
{
throw new InvalidOperationException("JSONが空です。");
}
}
}

この例では、利用者に公開するのはJsonLoaderだけです。

InternalFileReaderJsonValidatorはライブラリ内部の実装なので、internalにしています。これにより、利用者は余計なクラスに迷わず、使うべきAPIを理解しやすくなります。

また、内部クラスを後からリファクタリングしても、外部利用者への影響を抑えやすくなります。

4. internalと他のアクセス修飾子の違い

4-1. privateとの違い

privateは、最も狭いアクセス範囲を持つ修飾子です。

privateメンバーは、同じクラスまたは同じ構造体の中からしかアクセスできません。

C#
public class UserService
{
private void Validate()
{
Console.WriteLine("検証処理");
}

public void Register()
{
Validate(); // 同じクラス内なのでアクセスできる
}
}

別のクラスからは、同じアセンブリ内であってもアクセスできません。

C#
public class UserController
{
public void Create()
{
var service = new UserService();
service.Validate(); // エラー
}
}

internalは同じアセンブリ内の別クラスからもアクセスできますが、privateは同じクラス内に限定されます。

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

クラス内部だけで使う処理ならprivate、同じプロジェクト内の複数クラスから使う処理ならinternalを検討します。

4-2. protectedとの違い

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

C#
public class BaseService
{
protected void Log(string message)
{
Console.WriteLine(message);
}
}

public class UserService : BaseService
{
public void Register()
{
Log("ユーザー登録"); // 派生クラスなのでアクセスできる
}
}

protectedは、継承関係を前提としたアクセス制御です。

一方、internalは継承関係ではなく、同じアセンブリかどうかでアクセス範囲を決めます。

修飾子基準
protected継承関係
internal同じアセンブリ

別アセンブリにある派生クラスから使わせたい場合はprotected、同じアセンブリ内で使わせたい場合はinternalを使います。

4-3. protected internalとの違い

protected internalは、protectedinternalを組み合わせた修飾子です。

アクセスできるのは、次のどちらかに当てはまるコードです。

条件アクセス可否
同じアセンブリ内のコードアクセスできる
別アセンブリの派生クラスアクセスできる
別アセンブリの無関係なクラスアクセスできない

Microsoft公式ドキュメントでも、protected internalは「同じアセンブリ」または「別アセンブリの派生クラス」からアクセスできるアクセシビリティとして説明されています。

C#
public class BaseService
{
protected internal void Log(string message)
{
Console.WriteLine(message);
}
}

protected internalは、protectedより広く、internalよりも場合によって広いアクセス範囲になります。

名前から「protectedかつinternal」と考えたくなりますが、実際には「protectedまたはinternal」と捉えると理解しやすいです。

4-4. private protectedとの違い

private protectedは、protected internalよりも狭いアクセス範囲を持ちます。

アクセスできるのは、「同じアセンブリ内にある派生クラス」です。

条件アクセス可否
同じアセンブリ内の派生クラスアクセスできる
同じアセンブリ内の無関係なクラスアクセスできない
別アセンブリの派生クラスアクセスできない

Microsoft公式ドキュメントでも、private protectedは「同じアセンブリ内の派生クラスに限定した protected アクセス」として説明されています。

C#
public class BaseService
{
private protected void LogInternal(string message)
{
Console.WriteLine(message);
}
}

public class UserService : BaseService
{
public void Register()
{
LogInternal("登録処理"); // 同じアセンブリ内の派生クラスならアクセスできる
}
}

private protectedは、継承先に使わせたいが、別アセンブリの派生クラスには公開したくない場合に使います。

4-5. アクセス修飾子の比較表

C#の主なアクセス修飾子を比較すると、次のようになります。

アクセス修飾子アクセスできる範囲主な用途
publicどこからでもアクセス可能外部公開API
internal同じアセンブリ内のみプロジェクト内部の実装
private同じクラスまたは構造体内のみクラス内部の処理
protected同じクラスまたは派生クラス継承先に使わせる処理
protected internal同じアセンブリ、または別アセンブリの派生クラス広めに公開したい継承用メンバー
private protected同じアセンブリ内の派生クラス限定的に継承先へ公開するメンバー

迷ったときは、まずできるだけ狭いアクセス範囲を選ぶのが基本です。

外部に公開する必要がないならpublicにしない。クラス内だけで済むならprivateにする。同じアセンブリ内の複数クラスで使うならinternalにする。この考え方を持つと、設計が整理しやすくなります。

5. internalにアクセスできない原因と対処法

5-1. 別アセンブリからアクセスしている

internalにアクセスできない最も多い原因は、別アセンブリからアクセスしていることです。

たとえば、次のようなプロジェクト構成を考えます。

App.Core
└── internal class UserValidator

App.Web
└── UserValidatorを使おうとしている

App.CoreにあるUserValidatorinternalの場合、App.Webからはアクセスできません。

C#
// App.Core
namespace App.Core;

internal class UserValidator
{
public bool IsValid(string name)
{
return !string.IsNullOrWhiteSpace(name);
}
}
C#
// App.Web
using App.Core;

namespace App.Web;

public class UserController
{
public void Create(string name)
{
var validator = new UserValidator(); // エラー
}
}

対処法は、目的によって変わります。

目的対処法
外部から正式に使わせたいpublicに変更する
テストプロジェクトだけに見せたいInternalsVisibleToを使う
内部実装なので外部から使わせたくない設計を見直し、publicなクラス経由で使う

単にエラーを消すためにpublicへ変更するのではなく、そのクラスを外部APIとして公開してよいかを考えることが大切です。

5-2. プロジェクト参照はあるがinternalの範囲外になっている

「プロジェクト参照を追加したのにinternalへアクセスできない」というケースもよくあります。

たとえば、App.WebからApp.Coreを参照しているとします。

App.Web → App.Core を参照

この状態で、App.Corepublicクラスにはアクセスできます。

C#
public class UserService
{
}

しかし、internalクラスにはアクセスできません。

C#
internal class UserValidator
{
}

プロジェクト参照は、「別アセンブリの公開された型を利用できるようにする」ものです。internalのアクセス範囲を広げるものではありません。

そのため、参照を追加してもinternalメンバーは見えません。

別プロジェクトから使う必要があるなら、publicにするか、InternalsVisibleToを設定する必要があります。

5-3. 名前空間とアセンブリを混同している

internalを理解するうえで特に重要なのが、namespaceとアセンブリの違いです。

namespaceは、クラス名の衝突を防ぎ、コードを論理的に整理するための仕組みです。

一方、アセンブリは、ビルドされた.dll.exeの単位です。

次のようにnamespaceが違っていても、同じアセンブリ内であればinternalにアクセスできます。

C#
namespace App.Core.Security;

internal class PasswordHasher
{
public string Hash(string password)
{
return $"hashed_{password}";
}
}
C#
namespace App.Core.Services;

using App.Core.Security;

public class UserService
{
public void Register(string password)
{
var hasher = new PasswordHasher(); // 同じアセンブリならOK
}
}

逆に、namespaceが同じでも、別アセンブリならinternalにはアクセスできません。

App.Core.dll
namespace App.Shared

App.Web.dll
namespace App.Shared

同じApp.Sharedというnamespaceを使っていても、アセンブリが別ならinternalの壁は越えられません。

internalの判断基準は、namespaceではなくアセンブリです。

5-4. publicに変更すべきケース

internalにアクセスできない場合、publicに変更すべきケースもあります。

たとえば、次のような場合です。

ケースpublicにすべき理由
別プロジェクトから通常機能として使う外部公開APIとして扱うべきだから
ライブラリ利用者に提供する機能利用者が直接呼び出す必要があるから
複数アセンブリで共有する共通クラスinternalでは範囲が狭すぎるから
外部から利用される前提で設計しているAPI契約として公開すべきだから

たとえば、共通ライブラリに定義したDateTimeProviderを複数プロジェクトで使いたい場合は、publicにするのが自然です。

C#
public class DateTimeProvider
{
public DateTime Now => DateTime.Now;
}

このように、外部から使うことが設計上正しいなら、publicに変更して問題ありません。

ただし、publicにすると外部コードから依存される可能性があります。名前、引数、戻り値、例外の仕様などを簡単に変更しづらくなる点には注意しましょう。

5-5. InternalsVisibleToを使うべきケース

publicにはしたくないが、特定の別アセンブリからだけinternalにアクセスしたい場合は、InternalsVisibleToを使います。

代表的なのは、単体テストプロジェクトから本体プロジェクトのinternalメンバーをテストしたい場合です。

MyApp.Core
MyApp.Core.Tests

MyApp.Coreの内部実装をMyApp.Core.Testsからテストしたい場合、対象クラスをpublicにするのではなく、InternalsVisibleToを設定します。

Microsoft公式ドキュメントでも、InternalsVisibleToAttributeは通常は現在のアセンブリ内でしか見えない型やメンバーを、指定したアセンブリからも見えるようにする属性と説明されています。

C#
using System.Runtime.CompilerServices;

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

これにより、MyApp.Core.TestsからMyApp.Coreinternalクラスやメンバーにアクセスできるようになります。

ただし、これはあくまで「特定のアセンブリに内部実装を見せる」設定です。無条件にアクセス範囲を広げるものではないため、使いどころを慎重に判断しましょう。

6. internalをテストコードから使う方法

6-1. 単体テストでinternalメンバーにアクセスしたい場面

単体テストでは、internalメンバーを直接テストしたくなることがあります。

たとえば、次のような内部ロジックがあるとします。

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

このTaxCalculatorはアプリケーション内部の実装なので、外部には公開したくありません。しかし、税計算は重要なロジックなので、単体テストでは直接検証したい場合があります。

このとき、テストのためだけにpublicへ変更するのはおすすめできません。

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

こうしてしまうと、本来は外部に見せたくないクラスまで公開APIになってしまいます。

このような場面では、InternalsVisibleToを使って、テストプロジェクトからだけinternalにアクセスできるようにします。

6-2. InternalsVisibleTo属性とは

InternalsVisibleToは、指定したアセンブリに対してinternalメンバーを公開するための属性です。

正式には、System.Runtime.CompilerServices.InternalsVisibleToAttributeという属性です。

たとえば、MyApp.CoreinternalメンバーをMyApp.Core.Testsから使いたい場合、MyApp.Core側に次のように指定します。

C#
using System.Runtime.CompilerServices;

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

ポイントは、「アクセスされる側のアセンブリ」に設定することです。

MyApp.Core        ← ここにInternalsVisibleToを書く
MyApp.Core.Tests ← internalへアクセスする側

テストプロジェクト側ではなく、本体プロジェクト側に設定する必要があります。

6-3. AssemblyInfo.csまたは.csprojでの設定方法

InternalsVisibleToの設定方法は、主に2つあります。

1つ目は、AssemblyInfo.csに書く方法です。

C#
using System.Runtime.CompilerServices;

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

古い形式の.NET Frameworkプロジェクトなどでは、Properties/AssemblyInfo.csに記述することがよくあります。

2つ目は、.csprojに書く方法です。

XML
<ItemGroup>
<InternalsVisibleTo Include="MyApp.Core.Tests" />
</ItemGroup>

SDKスタイルの.NETプロジェクトでは、.csprojにこのように書くこともできます。

たとえば、MyApp.Core.csprojに次のように設定します。

XML
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<InternalsVisibleTo Include="MyApp.Core.Tests" />
</ItemGroup>

</Project>

これで、MyApp.Core.TestsからMyApp.Coreinternalクラスやメソッドにアクセスできます。

C#
public class TaxCalculatorTests
{
[Fact]
public void Calculate_ReturnsTaxIncludedPrice()
{
var calculator = new TaxCalculator();

decimal result = calculator.Calculate(1000m);

Assert.Equal(1100m, result);
}
}

6-4. テストのためにpublicへ変更しないほうがよい理由

テストコードからアクセスできないからといって、安易にinternalpublicへ変更するのは避けたほうがよいです。

理由は、publicにすると外部に公開するAPIになってしまうからです。

C#
// 本当は内部実装なのにpublicにしてしまう
public class TaxCalculator
{
public decimal Calculate(decimal price)
{
return price * 1.1m;
}
}

publicにしたクラスは、別プロジェクトやライブラリ利用者から直接使えるようになります。すると、後から実装を変えたいときに影響範囲が広がります。

たとえば、クラス名を変える、メソッドの引数を変える、別の設計に置き換える、といった変更がしづらくなります。

テストのためだけにアクセス範囲を広げると、設計上の意図が崩れてしまいます。

そのため、外部公開する必要がないものはinternalのままにし、テストプロジェクトだけにInternalsVisibleToで公開するほうが自然です。

6-5. InternalsVisibleTo使用時の注意点

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

まず、公開先のアセンブリ名を正しく指定する必要があります。

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

ここで指定するのは、プロジェクト名ではなくアセンブリ名です。通常はプロジェクト名と同じですが、.csprojAssemblyNameを変更している場合は異なることがあります。

XML
<PropertyGroup>
<AssemblyName>MyApp.Tests.Unit</AssemblyName>
</PropertyGroup>

この場合は、次のように実際のアセンブリ名を指定します。

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

また、強い名前付きアセンブリを使っている場合は、公開キーの指定が必要になることがあります。

さらに、InternalsVisibleToを多用すると、internalの意味が弱くなります。多くのアセンブリに内部実装を見せてしまうと、実質的にpublicに近い状態になってしまいます。

基本的には、テストプロジェクトや密接に関連するフレンドアセンブリに限定して使うのがよいでしょう。

7. internalを使うメリット・デメリット

7-1. 外部に公開したくない実装を隠せる

internalの大きなメリットは、外部に公開したくない実装を隠せることです。

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

C#
internal class RetryPolicy
{
public void Execute(Action action)
{
for (int i = 0; i < 3; i++)
{
try
{
action();
return;
}
catch
{
if (i == 2)
{
throw;
}
}
}
}
}

このクラスは、アプリケーション内部のリトライ処理として使うものであり、外部に直接使わせる必要がないかもしれません。

そのような場合、internalにしておけば、同じアセンブリ内では再利用しつつ、外部からの直接利用は防げます。

外部に公開するAPIが増えすぎると、利用者がどれを使えばよいかわかりにくくなります。internalを使うことで、見せるべきものと隠すべきものを分けられます。

7-2. APIの利用範囲を整理できる

internalを使うと、APIの利用範囲を整理できます。

たとえば、ライブラリの利用者には次のクラスだけを使ってほしいとします。

C#
public class PaymentClient
{
public void Pay()
{
var signer = new RequestSigner();
signer.Sign();

Console.WriteLine("決済を実行しました。");
}
}

内部では署名処理用のRequestSignerを使っています。

C#
internal class RequestSigner
{
public void Sign()
{
Console.WriteLine("リクエストに署名しました。");
}
}

RequestSignerpublicにしてしまうと、ライブラリ利用者が直接使えるようになります。しかし、署名処理の詳細はライブラリ内部で管理したい場合、外部に公開すべきではありません。

internalにすることで、利用者にはPaymentClientだけを見せ、内部処理は隠すことができます。

これにより、ライブラリの使い方がわかりやすくなります。

7-3. 保守性や安全性を高められる

internalは、保守性や安全性の向上にも役立ちます。

publicなクラスやメソッドは、外部から使われる可能性があります。そのため、後から仕様を変更すると外部コードに影響を与えるかもしれません。

一方、internalであれば、利用範囲が同じアセンブリ内に限定されます。影響範囲を把握しやすいため、リファクタリングもしやすくなります。

また、外部から呼ばれるべきでないメソッドをinternalにしておけば、誤用を防げます。

C#
public class AccountService
{
public void Withdraw(int amount)
{
ValidateAmount(amount);
Console.WriteLine($"{amount}円を引き出しました。");
}

internal void ValidateAmount(int amount)
{
if (amount <= 0)
{
throw new ArgumentException("金額が不正です。");
}
}
}

この例では、外部にはWithdrawを使ってほしいとします。ValidateAmountを外部に公開する必要はありません。

internalにすることで、外部の利用者に余計なメソッドを見せずに済みます。

7-4. アセンブリ設計を理解していないと混乱しやすい

internalのデメリットは、アセンブリの概念を理解していないと混乱しやすいことです。

特に、次のような誤解がよくあります。

誤解正しい理解
同じnamespaceならアクセスできるnamespaceは関係ない
プロジェクト参照があればアクセスできる別アセンブリなら基本的に不可
同じソリューション内ならアクセスできるソリューションも関係ない
フォルダが近ければアクセスできるフォルダ構成も関係ない

internalのアクセス範囲は、あくまで同じアセンブリ内です。

Visual StudioやRiderなどでは、複数プロジェクトを1つのソリューションにまとめることが多いため、「同じソリューション内なら使える」と思ってしまうことがあります。

しかし、ソリューションは開発環境上の管理単位であり、C#のアクセス制御とは直接関係ありません。

7-5. 過度にinternalを使うとテストや再利用がしにくくなる

internalは便利ですが、過度に使うとテストや再利用がしにくくなる場合があります。

たとえば、複数のプロジェクトで共通利用したい処理をinternalにしてしまうと、別プロジェクトから使えません。

C#
internal class StringNormalizer
{
public string Normalize(string value)
{
return value.Trim().ToLower();
}
}

この処理を複数アセンブリで使いたいなら、internalではなくpublicにするか、共通ライブラリとして設計する必要があります。

また、internalクラスが増えすぎると、アセンブリ内部の依存関係が複雑になることもあります。

「とりあえずinternalにする」のではなく、そのクラスやメソッドがどの範囲で使われるべきかを考えて選ぶことが大切です。

8. internalを使うべきケース・使わないほうがよいケース

8-1. 同じプロジェクト内だけで使う処理に適している

internalは、同じプロジェクト内だけで使う処理に適しています。

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

C#
internal class EmailTemplateBuilder
{
public string BuildWelcomeEmail(string userName)
{
return $"{userName}さん、ようこそ!";
}
}

このクラスがアプリケーション内部でしか使われないなら、internalにするのが自然です。

同じプロジェクト内の複数クラスから使いたいが、別プロジェクトには公開したくない。これがinternalの典型的な使いどころです。

8-2. 外部公開APIではpublicを使う

外部のプロジェクトやライブラリ利用者に使わせたいクラスは、publicにする必要があります。

C#
public class NotificationClient
{
public void Send(string message)
{
Console.WriteLine($"通知: {message}");
}
}

このクラスがライブラリ利用者に提供する正式なAPIであれば、publicが適切です。

internalにしてしまうと、別アセンブリから使えないため、ライブラリとしての役割を果たせません。

公開APIにするかどうかは、次のように考えると判断しやすいです。

質問答えがYesなら
別プロジェクトから直接使う必要があるかpublicを検討
利用者に仕様として提供するものかpublicを検討
将来も互換性を維持すべきものかpublicとして設計
内部実装にすぎないかinternalを検討

8-3. クラス内部だけで使うならprivateを使う

クラス内部だけで使うメソッドやフィールドなら、internalではなくprivateを使うべきです。

C#
public class UserService
{
public void Register(string name)
{
Validate(name);
Console.WriteLine("登録しました。");
}

private void Validate(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("名前が空です。");
}
}
}

ValidateUserServiceの中だけで使われるため、privateで十分です。

これをinternalにしてしまうと、同じアセンブリ内の他のクラスからも呼べるようになります。

C#
internal void Validate(string name)
{
}

不要にアクセス範囲を広げると、想定外の使われ方をする可能性があります。

アクセス修飾子は、必要最小限の範囲にするのが基本です。

8-4. 継承先で使わせたいならprotectedを検討する

派生クラスに使わせたいメンバーには、protectedを検討します。

C#
public abstract class BaseController
{
protected void Log(string message)
{
Console.WriteLine(message);
}
}

public class UserController : BaseController
{
public void Create()
{
Log("ユーザー作成");
}
}

このように、基底クラスの機能を派生クラスから使わせたい場合は、internalよりもprotectedのほうが意図を表しやすいです。

internalはアセンブリ単位の制御、protectedは継承関係の制御です。

同じアセンブリ内のクラス全体に使わせたいならinternal、派生クラスだけに使わせたいならprotectedを選びます。

8-5. 判断に迷ったときの選び方

アクセス修飾子に迷ったときは、次の順番で考えると整理しやすくなります。

質問選択肢
外部から使わせる必要があるかYesならpublic
同じクラス内だけで使うかYesならprivate
派生クラスに使わせたいかYesならprotected
同じアセンブリ内だけで使うかYesならinternal
同じアセンブリ内、または派生クラスに使わせたいかprotected internal
同じアセンブリ内の派生クラスだけに使わせたいかprivate protected

基本方針は、「必要以上に広く公開しない」ことです。

最初から何でもpublicにすると、外部から依存され、後で変更しにくくなります。

一方で、何でもprivateにすると、同じアセンブリ内で再利用しにくくなります。

internalは、その中間として「プロジェクト内部の実装」を表現するのに適した修飾子です。

9. よくある質問

9-1. internalと省略時のアクセス修飾子は同じ?

場合によります。

namespace直下に定義するクラスなどは、アクセス修飾子を省略すると基本的にinternalになります。

C#
class UserService
{
}

これは次のように書いた場合と近い意味です。

C#
internal class UserService
{
}

しかし、クラス内のメソッドやフィールドは、省略すると基本的にprivateになります。

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

このValidateinternalではなくprivateです。

そのため、「省略時はすべてinternal」と覚えるのは間違いです。定義する場所によってデフォルトが変わる点に注意しましょう。

9-2. internalクラスは別プロジェクトから使える?

通常は使えません。

別プロジェクトは、基本的に別アセンブリとしてビルドされます。internalは同じアセンブリ内からのみアクセスできるため、別プロジェクトからはアクセスできません。

ProjectA.dll
internal class Sample

ProjectB.dll
Sampleを使おうとする → エラー

別プロジェクトから使いたい場合は、次のいずれかを検討します。

方法用途
publicに変更する外部公開APIとして使わせたい場合
InternalsVisibleToを使うテストプロジェクトなど特定のアセンブリにだけ見せたい場合
設計を見直す内部実装を直接使わせるべきでない場合

9-3. internalとnamespaceは関係ある?

internalとnamespaceは直接関係ありません。

namespaceが同じでも、別アセンブリならinternalにはアクセスできません。

逆に、namespaceが違っても、同じアセンブリ内であればinternalにアクセスできます。

C#
namespace App.A;

internal class InternalClass
{
}
C#
namespace App.B;

using App.A;

public class Sample
{
public void Test()
{
var obj = new InternalClass(); // 同じアセンブリならOK
}
}

internalのアクセス範囲を決めるのは、namespaceではなくアセンブリです。

9-4. internalはDLLの外から見えない?

基本的には、DLLの外から直接アクセスできません。

C#の通常のコードから見ると、internalな型やメンバーは同じアセンブリ内に限定されます。そのため、別DLLや別プロジェクトからはアクセスできません。

ただし、リフレクションなどの仕組みを使えば、技術的に情報を取得できる場合はあります。

しかし、通常のC#コードとしては、internalは外部アセンブリから利用できないものとして扱います。

また、InternalsVisibleToを設定した場合は、指定したアセンブリからinternalにアクセスできるようになります。

9-5. internalをpublicに変えても問題ない?

必ずしも問題ないとは限りません。

internalpublicに変えると、外部アセンブリからアクセスできるようになります。つまり、内部実装だったものが外部公開APIになります。

次のような場合は、publicにしてもよい可能性があります。

ケース判断
外部から直接使う必要があるpublicを検討
ライブラリの正式な機能として提供するpublicが適切
複数プロジェクトで共通利用するpublicを検討

一方で、次のような場合はpublicにしないほうがよいです。

ケース理由
内部処理用の補助クラス外部に使わせる必要がない
仕様変更の可能性が高い外部依存が増えると変更しづらい
テストのためだけに使いたいInternalsVisibleToのほうが適切
外部から呼ばれると不整合が起きる誤用を招く可能性がある

publicに変更する前に、そのクラスやメソッドを外部に公開する責任を持てるかを考えましょう。

まとめ

C#のinternalは、型やメンバーを同じアセンブリ内からのみアクセスできるようにするアクセス修飾子です。

同じプロジェクト内だけで使いたいクラスやメソッド、外部に公開したくない内部実装、ライブラリの補助クラスなどに適しています。

publicはどこからでもアクセスできる外部公開用の修飾子ですが、internalはアセンブリ内部に限定するため、APIの公開範囲を整理しやすくなります。

一方で、internalのアクセス範囲はnamespaceではなくアセンブリで決まるため、同じnamespaceでも別プロジェクトならアクセスできない点に注意が必要です。

テストコードからinternalメンバーにアクセスしたい場合は、安易にpublicへ変更するのではなく、InternalsVisibleToを使う方法があります。

internalを適切に使うことで、外部に見せるAPIと内部実装を分離でき、保守性や安全性の高いC#コードを書きやすくなります。