C#の静的クラスとは?使い方・メリット・通常クラスとの違いを初心者向けにわかりやすく解説
はじめに
C#で共通処理をまとめる方法の一つに「静的クラス(static class)」があります。静的クラスは、通常のクラスとは異なり、newを使ってインスタンスを作成せずに利用できるクラスです。
たとえば、数値計算、文字列変換、入力値の検証など、オブジェクトごとの状態を必要としない処理をまとめる場合に適しています。
一方で、静的クラスを多用すると、単体テストが難しくなったり、複数の処理から共有される状態が不具合の原因になったりすることがあります。そのため、特徴を理解したうえで適切に使い分けることが重要です。
この記事では、C#の静的クラスについて、基本構文、使い方、通常クラスとの違い、メリット、デメリットを初心者向けに解説します。
1. C#の静的クラス(static class)とは
1-1. 静的クラスはインスタンスを作らずに利用するクラス
C#の静的クラスとは、インスタンスを作成せず、クラス名から直接メソッドやプロパティを利用するクラスです。
通常クラスは、次のようにnewを使ってインスタンスを作成します。
C#var user = new User();
user.ShowName();
一方、静的クラスはインスタンス化する必要がありません。
C#Calculator.Add(10, 20);
静的クラスは、クラスそのものに処理やデータが属していると考えると理解しやすいでしょう。
また、静的クラスをnewでインスタンス化しようとするとコンパイルエラーになります。
C#var calculator = new Calculator(); // コンパイルエラー
1-2. 静的クラスの基本構文
静的クラスは、クラス宣言にstaticキーワードを付けて定義します。
C#public static class Calculator
{
public static int Add(int x, int y)
{
return x + y;
}
}
呼び出すときは、クラス名の後ろにメンバー名を指定します。
C#int result = Calculator.Add(10, 20);
Console.WriteLine(result);
実行結果は次のとおりです。
30
静的クラス内に定義するフィールド、プロパティ、メソッドなどのメンバーは、すべて静的メンバーでなければなりません。
1-3. 静的クラスが適している主な用途
C#の静的クラスは、主に次のような用途に適しています。
数値の計算
単位やデータ形式の変換
文字列の整形
入力値の検証
定数や読み取り専用データの管理
複数のクラスから利用する共通処理
拡張メソッドの定義
たとえば、消費税込みの金額を計算する処理は、利用者ごとの状態を持つ必要がありません。そのため、次のような静的クラスとして定義できます。
C#public static class PriceCalculator
{
public static decimal CalculateTaxIncluded(
decimal price,
decimal taxRate)
{
return price * (1 + taxRate);
}
}
ただし、ユーザー名や注文内容など、オブジェクトごとに異なるデータを保持する処理には通常クラスのほうが適しています。
1-4. staticキーワードの役割
staticキーワードは、メンバーが個別のインスタンスではなく、型そのものに属していることを表します。
C#では、staticを次のような要素に指定できます。
C#public static class SampleClass
{
public static int SampleField;
public static string SampleProperty { get; set; } = "";
public static void SampleMethod()
{
}
}
静的メンバーには、クラス名から直接アクセスします。
C#SampleClass.SampleField = 10;
SampleClass.SampleProperty = "C#";
SampleClass.SampleMethod();
通常クラスのメンバーにもstaticを付けられますが、静的クラスの場合はすべてのメンバーが静的である必要があります。
2. C#で静的クラスを作成・使用する方法
2-1. static classを宣言する
静的クラスを作成するときは、classの前にstaticを記述します。
C#public static class MessageHelper
{
}
アクセス修飾子を省略した場合、名前空間の直下に定義したクラスは既定でinternalになります。
C#static class MessageHelper
{
}
別のプロジェクトやアセンブリから利用する可能性がある場合は、public static classとして宣言します。
2-2. 静的メソッドを定義して呼び出す
静的クラス内のメソッドにはstaticを付けます。
C#public static class MessageHelper
{
public static string CreateGreeting(string name)
{
return $"こんにちは、{name}さん";
}
}
呼び出すときは、インスタンスを作成せず、クラス名から直接アクセスします。
C#string message = MessageHelper.CreateGreeting("田中");
Console.WriteLine(message);
実行結果は次のとおりです。
こんにちは、田中さん
静的メソッドの呼び出しには、次のような特徴があります。
newが不要クラス名から直接呼び出せる
インスタンスの状態に依存しない処理に適している
同じ処理を複数の場所から利用しやすい
2-3. 静的フィールドと静的プロパティを定義する
静的クラスには、静的フィールドや静的プロパティも定義できます。
C#public static class AppSettings
{
public static string ApplicationName { get; set; }
= "Sample App";
public static int MaxRetryCount { get; set; } = 3;
}
利用するときは、クラス名からアクセスします。
C#Console.WriteLine(AppSettings.ApplicationName);
Console.WriteLine(AppSettings.MaxRetryCount);
ただし、どこからでも値を変更できる静的プロパティは、プログラム全体で共有されるグローバルな状態になります。
C#AppSettings.MaxRetryCount = 100;
ある処理が値を書き換えると、別の処理にも影響します。変更する必要がない値は、読み取り専用にすることが大切です。
C#public static class AppSettings
{
public static string ApplicationName { get; }
= "Sample App";
public static int MaxRetryCount { get; } = 3;
}
コンパイル時に値が決まる定数には、constも使用できます。
C#public static class AppConstants
{
public const int MaxLength = 100;
}
実行時に値を設定し、その後変更させたくない場合は、static readonlyフィールドを利用できます。
C#public static class AppConstants
{
public static readonly DateTime StartedAt = DateTime.Now;
}
2-4. 静的コンストラクターで初期化処理を行う
静的コンストラクターは、静的クラスや静的メンバーを初期化するための特別なコンストラクターです。
C#public static class SystemInformation
{
public static DateTime InitializedAt { get; }
static SystemInformation()
{
InitializedAt = DateTime.Now;
}
}
静的コンストラクターには、次の特徴があります。
アクセス修飾子を指定できない
引数を指定できない
プログラムから直接呼び出せない
通常は型が初めて利用される前に一度だけ実行される
次のように利用します。
C#Console.WriteLine(SystemInformation.InitializedAt);
静的コンストラクターは、初期化に複数の処理が必要な場合に便利です。
C#public static class ApplicationEnvironment
{
public static string EnvironmentName { get; }
public static string BaseDirectory { get; }
static ApplicationEnvironment()
{
EnvironmentName =
Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")
?? "Production";
BaseDirectory = AppContext.BaseDirectory;
}
}
静的コンストラクター内で例外が発生すると、その型を正常に使用できなくなる可能性があります。ファイルの読み込みや外部サービスへの接続など、失敗する可能性がある処理を実行する場合は注意が必要です。
2-5. 実行できるサンプルコードで使い方を確認する
次のコードは、そのままコンソールアプリケーションで実行できる静的クラスのサンプルです。
C#using System;
public static class TemperatureConverter
{
public static int ConversionCount { get; private set; }
static TemperatureConverter()
{
ConversionCount = 0;
Console.WriteLine("TemperatureConverterを初期化しました。");
}
public static double CelsiusToFahrenheit(double celsius)
{
ConversionCount++;
return celsius * 9 / 5 + 32;
}
public static double FahrenheitToCelsius(double fahrenheit)
{
ConversionCount++;
return (fahrenheit - 32) * 5 / 9;
}
}
public class Program
{
public static void Main()
{
double fahrenheit =
TemperatureConverter.CelsiusToFahrenheit(25);
double celsius =
TemperatureConverter.FahrenheitToCelsius(86);
Console.WriteLine($"25℃は{fahrenheit}℉です。");
Console.WriteLine($"86℉は{celsius}℃です。");
Console.WriteLine(
$"変換回数:{TemperatureConverter.ConversionCount}回");
}
}
実行結果の例は次のとおりです。
TemperatureConverterを初期化しました。
25℃は77℉です。
86℉は30℃です。
変換回数:2回
この例では、変換処理を静的メソッドとして定義しています。また、静的プロパティを使って変換回数をクラス全体で共有しています。
ただし、変換回数のような変更可能な状態を静的クラスに持たせる場合は、テストや並列処理への影響を考える必要があります。
3. 静的クラスと通常クラスの違い
3-1. インスタンス化できるかどうかの違い
通常クラスは、newを使って複数のインスタンスを作成できます。
C#public class User
{
public string Name { get; set; } = "";
}
C#var user1 = new User { Name = "田中" };
var user2 = new User { Name = "佐藤" };
user1とuser2は、それぞれ独立した状態を持ちます。
一方、静的クラスはインスタンス化できません。
C#public static class UserHelper
{
}
C#var helper = new UserHelper(); // コンパイルエラー
静的クラスは、クラス名を通して直接利用することを前提に設計されています。
3-2. インスタンスメンバーを定義できるかどうかの違い
通常クラスには、インスタンスフィールド、インスタンスプロパティ、インスタンスメソッドを定義できます。
C#public class Counter
{
public int Value { get; private set; }
public void Increment()
{
Value++;
}
}
インスタンスごとに異なる値を保持できます。
C#var counter1 = new Counter();
var counter2 = new Counter();
counter1.Increment();
counter1.Increment();
counter2.Increment();
Console.WriteLine(counter1.Value); // 2
Console.WriteLine(counter2.Value); // 1
静的クラスには、インスタンスメンバーを定義できません。
C#public static class Counter
{
public int Value { get; set; } // コンパイルエラー
}
静的クラス内のメンバーにはstaticが必要です。
C#public static class Counter
{
public static int Value { get; set; }
}
この場合、Valueはプログラム内で共有される一つの値です。
3-3. 継承やインターフェース実装に関する違い
通常クラスは、別のクラスを継承できます。
C#public class Animal
{
public void Eat()
{
Console.WriteLine("食べます。");
}
}
public class Dog : Animal
{
}
また、インターフェースを実装できます。
C#public interface ILogger
{
void Log(string message);
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
一方、静的クラスは別のクラスを継承できず、継承元としても利用できません。また、インターフェースも実装できません。
C#public static class ConsoleLogger : ILogger // コンパイルエラー
{
}
そのため、インターフェースを利用して実装を差し替えたい処理には、静的クラスは適していません。
3-4. メモリ上での扱いと利用期間の違い
通常クラスのインスタンスは、newによって生成されます。インスタンスがどこからも参照されなくなると、ガベージコレクションの対象になります。
C#var user = new User();
一方、静的クラスはインスタンスを生成しません。静的フィールドに格納したデータは型に関連付けられ、通常は型が読み込まれている間保持されます。
C#public static class Cache
{
public static readonly Dictionary<string, string> Data = new();
}
静的フィールドからオブジェクトへの参照が残っている間、そのオブジェクトはガベージコレクションで解放されません。
そのため、大量のデータを静的フィールドに保存し続けると、メモリ使用量が増える可能性があります。静的クラスだから自動的にメモリ効率が良くなるわけではありません。
3-5. 静的クラスと通常クラスの違いを比較表で確認する
| 比較項目 | 静的クラス | 通常クラス |
|---|---|---|
| 宣言方法 | static class | class |
| インスタンス化 | できない | できる |
| インスタンスメンバー | 定義できない | 定義できる |
| 静的メンバー | 定義できる | 定義できる |
| インスタンスごとの状態 | 持てない | 持てる |
| クラスの継承 | できない | できる |
| 継承元としての利用 | できない | sealedでなければ可能 |
| インターフェース実装 | できない | できる |
| 主な用途 | 共通処理、変換、検証、拡張メソッド | 状態を持つオブジェクト、差し替え可能な処理 |
| 呼び出し方 | クラス名.メンバー名 | インスタンス名.メンバー名 |
4. 静的クラスと静的メンバーの違い
4-1. 静的クラスはすべてのメンバーがstaticになる
静的クラスに定義できるメンバーは、すべて静的メンバーです。
C#public static class TextHelper
{
public static string TrimAndLower(string value)
{
return value.Trim().ToLowerInvariant();
}
}
静的クラス内にインスタンスメソッドを定義すると、コンパイルエラーになります。
C#public static class TextHelper
{
public string TrimText(string value) // コンパイルエラー
{
return value.Trim();
}
}
静的クラスは、クラス全体がインスタンスを必要としない処理で構成されていることを明確に表します。
4-2. 通常クラスにも静的メンバーを定義できる
staticメンバーは、静的クラスだけでなく通常クラスにも定義できます。
C#public class User
{
public static int CreatedCount { get; private set; }
public string Name { get; }
public User(string name)
{
Name = name;
CreatedCount++;
}
}
Nameはインスタンスごとに異なりますが、CreatedCountはすべてのインスタンスで共有されます。
C#var user1 = new User("田中");
var user2 = new User("佐藤");
Console.WriteLine(user1.Name); // 田中
Console.WriteLine(user2.Name); // 佐藤
Console.WriteLine(User.CreatedCount); // 2
このように、通常クラスではインスタンスメンバーと静的メンバーを組み合わせられます。
4-3. staticメソッドからインスタンスメンバーを直接呼び出せない理由
静的メソッドは、特定のインスタンスに属していません。そのため、どのインスタンスのメンバーを利用すればよいか判断できず、インスタンスメンバーを直接呼び出せません。
C#public class User
{
public string Name { get; set; } = "";
public static void ShowName()
{
Console.WriteLine(Name); // コンパイルエラー
}
}
Nameはインスタンスごとに異なります。静的メソッドの中では、対象となるインスタンスが決まっていないため、直接アクセスできません。
インスタンスメンバーを利用するには、対象のインスタンスを引数として受け取る方法があります。
C#public class User
{
public string Name { get; set; } = "";
public static void ShowName(User user)
{
Console.WriteLine(user.Name);
}
}
C#var user = new User
{
Name = "田中"
};
User.ShowName(user);
また、静的メソッド内でインスタンスを作成して利用することも可能です。
C#public static void CreateAndShowUser()
{
var user = new User
{
Name = "田中"
};
Console.WriteLine(user.Name);
}
「静的メソッドからインスタンスメンバーを一切利用できない」のではなく、「対象となるインスタンスを指定せずに直接利用できない」と理解しましょう。
4-4. 静的クラスと静的メンバーの使い分け
クラス内のすべての処理がインスタンスを必要としない場合は、静的クラスが候補になります。
C#public static class NumberConverter
{
public static int ToInt(string value)
{
return int.Parse(value);
}
}
一方、インスタンスごとの状態を持ちながら、一部のデータだけを共有したい場合は、通常クラスに静的メンバーを定義します。
C#public class Product
{
public static int ProductCount { get; private set; }
public string Name { get; }
public decimal Price { get; }
public Product(string name, decimal price)
{
Name = name;
Price = price;
ProductCount++;
}
}
判断の目安は次のとおりです。
| 状況 | 適した方法 |
|---|---|
| すべての処理が状態を必要としない | 静的クラス |
| インスタンスごとに異なる状態を持つ | 通常クラス |
| 一部の値だけ全インスタンスで共有する | 通常クラスの静的メンバー |
| 実装をインターフェースで差し替えたい | 通常クラス |
| 拡張メソッドを定義したい | 静的クラス |
5. C#で静的クラスを使用するメリット
5-1. インスタンス生成なしで簡単に呼び出せる
静的クラスは、newを使わずにクラス名から直接利用できます。
C#int result = Math.Abs(-10);
独自の静的クラスも同じように呼び出せます。
C#public static class TaxCalculator
{
public static decimal AddTax(
decimal price,
decimal taxRate)
{
return price * (1 + taxRate);
}
}
C#decimal total = TaxCalculator.AddTax(1000m, 0.1m);
Console.WriteLine(total);
単純な共通処理を呼び出すためだけにインスタンスを作成する必要がないため、コードを簡潔に記述できます。
5-2. 共通処理を一か所に集約できる
複数のクラスで同じ処理を記述すると、修正箇所が増えてコードの重複が発生します。
静的クラスに処理をまとめれば、共通処理を一か所で管理できます。
C#public static class EmailValidator
{
public static bool IsValid(string email)
{
if (string.IsNullOrWhiteSpace(email))
{
return false;
}
return email.Contains('@');
}
}
C#bool result1 = EmailValidator.IsValid("user@example.com");
bool result2 = EmailValidator.IsValid("");
検証ルールを変更するときは、EmailValidatorを修正するだけで済みます。
ただし、実際のメールアドレス検証では、単純に@の有無を確認するだけでは不十分です。このコードは静的クラスの構造を示すための簡略化した例です。
5-3. 状態を持たない処理をわかりやすく表現できる
静的クラスを使用すると、そのクラスがインスタンスごとの状態を持たないことをコード上で明確に表現できます。
C#public static class DistanceConverter
{
public static double KilometersToMiles(double kilometers)
{
return kilometers * 0.621371;
}
}
距離の変換結果は、入力値だけで決まります。過去にどのような値を変換したか、誰が処理を実行したかといった状態を保存する必要はありません。
このような処理を静的クラスにすると、利用者はインスタンスの状態を意識せずに使用できます。
5-4. ユーティリティクラスの誤ったインスタンス化を防げる
通常クラスとしてユーティリティクラスを作ると、意図せずインスタンス化される可能性があります。
C#public class StringHelper
{
public static string RemoveSpaces(string value)
{
return value.Replace(" ", "");
}
}
このクラスはインスタンスを必要としませんが、次のコードを記述できてしまいます。
C#var helper = new StringHelper();
静的クラスに変更すれば、インスタンス化をコンパイラが禁止します。
C#public static class StringHelper
{
public static string RemoveSpaces(string value)
{
return value.Replace(" ", "");
}
}
これにより、「このクラスはインスタンスを作らずに使用する」という設計意図が明確になります。
5-5. 拡張メソッドを定義できる
C#の拡張メソッドを定義するには、静的クラスと静的メソッドを使用します。
拡張メソッドとは、既存の型を変更せずに、新しいメソッドを追加したような形式で呼び出せる機能です。
C#public static class StringExtensions
{
public static bool IsNotEmpty(this string? value)
{
return !string.IsNullOrEmpty(value);
}
}
定義した拡張メソッドは、通常のインスタンスメソッドのように呼び出せます。
C#string text = "C#";
bool result = text.IsNotEmpty();
Console.WriteLine(result);
実行結果は次のとおりです。
True
拡張メソッドを定義するクラスは、静的クラスである必要があります。また、拡張メソッド自体も静的メソッドとして定義し、第1引数にthisを付けます。
一般的な拡張メソッドは、名前空間の直下にある、入れ子ではない非ジェネリック静的クラスに定義します。
6. 静的クラスのデメリットと注意点
6-1. 継承やインターフェースによる差し替えができない
静的クラスは継承できず、インターフェースも実装できません。そのため、実行環境や用途に応じて処理を差し替える設計には向いていません。
たとえば、通知を送る処理を静的クラスとして作成したとします。
C#public static class NotificationService
{
public static void Send(string message)
{
Console.WriteLine(message);
}
}
この処理を、メール通知、チャット通知、テスト用通知などに差し替えることは簡単ではありません。
差し替えが必要な場合は、インターフェースと通常クラスを使用します。
C#public interface INotificationService
{
void Send(string message);
}
public class EmailNotificationService
: INotificationService
{
public void Send(string message)
{
Console.WriteLine($"メール送信:{message}");
}
}
public class TestNotificationService
: INotificationService
{
public void Send(string message)
{
Console.WriteLine($"テスト通知:{message}");
}
}
この構成なら、INotificationServiceを実装するクラスを入れ替えられます。
6-2. 単体テストでモック化しにくい
外部サービスへのアクセスや現在日時の取得を静的クラスに直接依存させると、単体テストで処理を置き換えにくくなります。
C#public static class CurrentTime
{
public static DateTime Now()
{
return DateTime.Now;
}
}
利用側がCurrentTime.Now()を直接呼び出すと、テスト時に任意の日時を返すように変更するのが難しくなります。
C#public class CampaignService
{
public bool IsCampaignPeriod()
{
DateTime now = CurrentTime.Now();
return now.Month == 12;
}
}
テストしやすくするには、インターフェースを利用して日時取得処理を差し替えられるようにします。
C#public interface IClock
{
DateTime Now { get; }
}
public class SystemClock : IClock
{
public DateTime Now => DateTime.Now;
}
public class CampaignService
{
private readonly IClock _clock;
public CampaignService(IClock clock)
{
_clock = clock;
}
public bool IsCampaignPeriod()
{
return _clock.Now.Month == 12;
}
}
テストでは、固定日時を返す実装を渡せます。
C#public class FixedClock : IClock
{
public DateTime Now { get; }
public FixedClock(DateTime now)
{
Now = now;
}
}
状態を持たない単純な計算や変換であれば静的メソッドでもテストしやすい一方、時刻、ファイル、データベース、ネットワークなどの外部要因に依存する処理は、差し替え可能な通常クラスにすることを検討しましょう。
6-3. グローバルな状態を持たせると不具合の原因になる
静的フィールドや書き換え可能な静的プロパティは、プログラム全体から共有されます。
C#public static class UserSession
{
public static string CurrentUserName { get; set; } = "";
}
どこからでも値を変更できるため、処理の実行順序によって結果が変化する可能性があります。
C#UserSession.CurrentUserName = "田中";
別の処理が値を変更すると、それ以降の処理にも影響します。
特にWebアプリケーションでは、静的フィールドが複数のリクエストやユーザー間で共有される可能性があります。ユーザーごとの情報を静的フィールドに保存すると、別のユーザーの情報と混ざる危険性があります。
変更可能なグローバル状態を避けるには、次のような対策が有効です。
変更不要な値は
constやstatic readonlyにする静的プロパティの
setを公開しないユーザーごとのデータを静的フィールドに保存しない
必要な値をメソッドの引数として渡す
状態管理が必要な処理は通常クラスに分離する
6-4. 複数スレッドから利用する場合は排他制御が必要になる
複数のスレッドが同じ静的フィールドを同時に変更すると、競合状態が発生する可能性があります。
C#public static class AccessCounter
{
public static int Count { get; private set; }
public static void Increment()
{
Count++;
}
}
Count++は、内部的には値の読み取り、加算、書き込みという複数の処理を含みます。複数スレッドから同時に実行されると、加算結果が失われることがあります。
単純な整数の加算には、Interlockedを利用できます。
C#using System.Threading;
public static class AccessCounter
{
private static int _count;
public static int Count => Volatile.Read(ref _count);
public static void Increment()
{
Interlocked.Increment(ref _count);
}
}
複数の操作をまとめて保護する必要がある場合は、lockを使用します。
C#public static class MessageStore
{
private static readonly object SyncRoot = new();
private static readonly List<string> Messages = new();
public static void Add(string message)
{
lock (SyncRoot)
{
Messages.Add(message);
}
}
public static string[] GetAll()
{
lock (SyncRoot)
{
return Messages.ToArray();
}
}
}
ただし、排他制御を追加すると設計が複雑になります。共有状態そのものを持たない設計にできないか、最初に検討することが重要です。
6-5. 静的クラスへの依存が増えると保守性が低下する
さまざまなクラスから同じ静的クラスを直接呼び出すと、アプリケーション全体がその静的クラスに強く依存します。
C#public static class DatabaseHelper
{
public static void Save(string data)
{
// データを保存する処理
}
}
多くのクラスがDatabaseHelper.Save()を直接呼び出す構成では、保存先の変更やテスト用実装への切り替えが難しくなります。
また、一つの静的クラスに多数の処理を集めると、責務が不明確な巨大クラスになりがちです。
C#public static class CommonUtil
{
// 文字列処理
// 日付処理
// ファイル処理
// データベース処理
// 通信処理
}
共通処理をまとめる場合でも、役割ごとに分割しましょう。
C#public static class StringHelper
{
}
public static class DateTimeHelper
{
}
public static class NumberConverter
{
}
処理の差し替えや外部依存が必要な機能については、インターフェースと通常クラスを利用した設計が適しています。
7. 静的クラスが向いているケースと向いていないケース
7-1. 計算・変換・検証など状態を持たない共通処理
静的クラスが特に向いているのは、入力値を受け取り、決まった結果を返す状態を持たない処理です。
代表的な例には、次のような処理があります。
数値計算
単位変換
文字列変換
書式設定
入力値検証
拡張メソッド
たとえば、文字列を数値に変換する処理は、入力値以外の状態を必要としません。
C#public static class NumberParser
{
public static int? ParseOrNull(string? value)
{
if (int.TryParse(value, out int result))
{
return result;
}
return null;
}
}
C#int? number1 = NumberParser.ParseOrNull("100");
int? number2 = NumberParser.ParseOrNull("abc");
Console.WriteLine(number1); // 100
Console.WriteLine(number2); // 空行
純粋な計算処理も静的クラスに適しています。
C#public static class RectangleCalculator
{
public static double CalculateArea(
double width,
double height)
{
if (width < 0)
{
throw new ArgumentOutOfRangeException(
nameof(width));
}
if (height < 0)
{
throw new ArgumentOutOfRangeException(
nameof(height));
}
return width * height;
}
}
一方、次のような処理は静的クラスに向いていない場合があります。
データベースや外部APIに接続する処理
実行環境によって実装を変更する処理
ユーザーごとの状態を保持する処理
モックに置き換えてテストしたい処理
複数の実装を使い分ける処理
設定や状態が頻繁に変化する処理
これらは、インターフェースと通常クラスを利用して、依存性注入によって差し替えられる構成にするほうが適しています。
7-2. 定数や読み取り専用データを管理する処理
静的クラスは、アプリケーション全体で共通して利用する定数をまとめる用途にも使用できます。
C#public static class ValidationConstants
{
public const int UserNameMaxLength = 50;
public const int PasswordMinLength = 8;
}
C#if (userName.Length > ValidationConstants.UserNameMaxLength)
{
Console.WriteLine("ユーザー名が長すぎます。");
}
実行時に決定する値を読み取り専用で保持する場合は、static readonlyを利用できます。
C#public static class ApplicationInformation
{
public static readonly string BaseDirectory =
AppContext.BaseDirectory;
}
読み取り専用プロパティとして公開する方法もあります。
C#public static class SupportedFormats
{
public static IReadOnlyList<string> Extensions { get; }
= new[] { ".jpg", ".png", ".gif" };
}
ただし、コレクションを読み取り専用のインターフェースで公開しても、元のコレクションが変更可能であれば、完全に不変とは限りません。外部から変更させたくない場合は、不変コレクションの利用や、配列・リストのコピーを返す設計も検討しましょう。
また、環境ごとに値が変化する設定情報や、テスト時に差し替えたい設定情報まで静的クラスにまとめると、柔軟性が低下します。固定値は静的クラス、変更可能な設定は設定オブジェクトとして管理するなど、性質に応じた使い分けが重要です。
まとめ
C#の静的クラスは、static classを使って宣言し、インスタンスを作成せずに利用するクラスです。
C#public static class Calculator
{
public static int Add(int x, int y)
{
return x + y;
}
}
C#int result = Calculator.Add(10, 20);
静的クラスには、次の特徴があります。
newによるインスタンス化ができないすべてのメンバーが静的メンバーになる
クラス名から直接メンバーを呼び出せる
別のクラスを継承できない
継承元として利用できない
インターフェースを実装できない
拡張メソッドを定義できる
計算、変換、検証など、状態を持たない共通処理には静的クラスが適しています。インスタンス化を禁止できるため、ユーティリティクラスとしての設計意図も明確になります。
一方で、変更可能な静的フィールドを持たせると、グローバルな状態による不具合やスレッド間の競合が発生する可能性があります。また、継承やインターフェースによる差し替えができないため、単体テストや将来的な仕様変更が難しくなることもあります。
「インスタンスごとの状態が必要か」「処理を差し替える可能性があるか」「外部サービスに依存するか」を基準に、静的クラスと通常クラスを適切に使い分けましょう。

