C# static classとは?使い方・通常クラスとの違い・注意点をサンプルコードで解説
はじめに
C#でクラスを定義するときは、通常のclassだけでなく、static classを使用できます。static class(静的クラス)は、インスタンスを生成せず、クラス名から直接メソッドやプロパティへアクセスするためのクラスです。
文字列の変換処理、数値計算、入力値の検証、アプリケーション全体で共有する設定値など、オブジェクトごとの状態を必要としない処理をまとめる場合に適しています。
一方、static classには、インスタンス化できない、継承できない、インスタンスメンバーを定義できないといった制約があります。また、変更可能なデータをstaticフィールドで共有すると、並列処理や単体テストで問題が発生することもあります。
この記事では、C#のstatic classの意味や使い方、通常クラスやstaticメンバーとの違い、利用時の注意点をサンプルコードとともに解説します。
1. C#のstatic class(静的クラス)とは
1-1. static classの基本的な意味
C#のstatic classとは、インスタンスを生成せずに利用することを前提としたクラスです。クラス宣言にstaticキーワードを付けて定義します。
C#public static class Calculator
{
public static int Add(int x, int y)
{
return x + y;
}
}
static classのメソッドを呼び出すときは、newでオブジェクトを作成する必要がありません。次のように、クラス名から直接アクセスします。
C#int result = Calculator.Add(10, 20);
Console.WriteLine(result); // 30
この例では、Calculatorクラス自体が計算処理の置き場所として機能しています。計算するたびに個別のオブジェクトを作る必要はありません。
1-2. static classはインスタンス化できない
通常クラスは、new演算子を使ってインスタンスを生成できます。
C#public class User
{
public string Name { get; set; } = string.Empty;
}
User user = new User();
一方、static classはインスタンス化できません。
C#public static class Calculator
{
}
次のコードはコンパイルエラーになります。
C#Calculator calculator = new Calculator();
static classには、オブジェクトごとのデータを保持するという考え方がありません。クラス名を通して、共有される機能やデータへアクセスします。
そのため、ユーザー、商品、注文など、個別の状態を持つオブジェクトを表現する用途には適していません。
1-3. static classのメンバーはすべてstaticになる
static class内に定義するフィールド、プロパティ、メソッド、イベントなどは、基本的にstaticメンバーでなければなりません。
C#public static class AppInfo
{
public static string Name { get; } = "Sample App";
public static void Show()
{
Console.WriteLine(Name);
}
}
static class内にインスタンスメソッドを定義しようとすると、コンパイルエラーになります。
C#public static class AppInfo
{
// コンパイルエラー
public void Show()
{
Console.WriteLine("アプリ情報");
}
}
正しくは、メソッドにもstaticを付けます。
C#public static class AppInfo
{
public static void Show()
{
Console.WriteLine("アプリ情報");
}
}
なお、定数を表すconstメンバーや入れ子型は、通常のstaticメンバーとは構文上の扱いが異なります。ただし、static classを利用する側から見ると、「インスタンスを作らず、クラスに属する機能を扱う」という理解で問題ありません。
1-4. static classが利用される代表的な場面
C#のstatic classは、主に次のような場面で利用されます。
数値計算や文字列変換などのユーティリティ処理
入力値を検証するバリデーション処理
アプリケーション共通の定数
オブジェクトの状態に依存しない変換処理
拡張メソッドの定義
アプリケーション全体で共有する読み取り専用情報
例えば、税込価格を計算する処理は、商品ごとの状態をstatic class内に保持する必要がありません。
C#public static class PriceCalculator
{
public static decimal AddTax(decimal price, decimal taxRate)
{
return price * (1 + taxRate);
}
}
呼び出し側では、クラス名から直接利用できます。
C#decimal priceWithTax = PriceCalculator.AddTax(1000m, 0.1m);
Console.WriteLine(priceWithTax); // 1100.0
ただし、static classを使えば常に設計が単純になるとは限りません。外部サービスへのアクセス、変更可能な共有データ、テスト時に差し替えたい処理などは、通常クラスとして設計したほうが扱いやすい場合があります。
2. C#でstatic classを定義・使用する方法
2-1. static classの基本構文
static classの基本構文は次のとおりです。
C#アクセス修飾子 static class クラス名
{
// staticフィールド
// staticプロパティ
// staticメソッド
}
実際のコードでは、次のように定義します。
C#public static class MessageHelper
{
public static string CreateGreeting(string name)
{
return $"こんにちは、{name}さん。";
}
}
呼び出すときは、クラス名、ドット、メンバー名の順に記述します。
C#string message = MessageHelper.CreateGreeting("田中");
Console.WriteLine(message);
実行結果は次のとおりです。
こんにちは、田中さん。
static classの名前には、そのクラスが提供する役割が分かる名前を付けることが重要です。UtilityやCommonのような広すぎる名前に多数の処理を詰め込むと、クラスの責務が分かりにくくなります。
例えば、次のように処理内容ごとにクラスを分けると理解しやすくなります。
C#public static class DateFormatter
{
public static string ToJapaneseDate(DateTime date)
{
return date.ToString("yyyy年MM月dd日");
}
}
public static class EmailValidator
{
public static bool IsValid(string email)
{
return !string.IsNullOrWhiteSpace(email)
&& email.Contains('@');
}
}
2-2. staticメソッドを定義して呼び出すサンプルコード
staticメソッドは、インスタンスを生成せずに呼び出せるメソッドです。
次の例では、2つの整数のうち大きい値を返すstaticメソッドを定義しています。
C#public static class NumberHelper
{
public static int Max(int x, int y)
{
return x >= y ? x : y;
}
}
呼び出し方は次のとおりです。
C#int result = NumberHelper.Max(15, 8);
Console.WriteLine(result); // 15
staticメソッドには引数を渡すことも、戻り値を設定することもできます。通常のメソッドと基本的な記述方法は同じです。
戻り値が不要な場合は、voidを指定します。
C#public static class Logger
{
public static void Write(string message)
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}");
}
}
C#Logger.Write("処理を開始しました。");
staticメソッドは、特定のインスタンスの状態に依存しない処理に適しています。引数を受け取り、計算や変換を行って結果を返す処理は、staticメソッドとして設計しやすい代表例です。
2-3. staticプロパティとstaticフィールドの使い方
static classには、staticプロパティやstaticフィールドも定義できます。
C#public static class ApplicationSettings
{
public static string ApplicationName { get; set; } = "Sample App";
public static int MaxRetryCount = 3;
}
呼び出し側では、クラス名から直接アクセスします。
C#Console.WriteLine(ApplicationSettings.ApplicationName);
Console.WriteLine(ApplicationSettings.MaxRetryCount);
ただし、publicなstaticフィールドをそのまま公開すると、どこからでも自由に値を書き換えられます。
C#ApplicationSettings.MaxRetryCount = -100;
意図しない値の設定を防ぐには、フィールドをprivateにし、プロパティを通して公開する方法が有効です。
C#public static class ApplicationSettings
{
private static int _maxRetryCount = 3;
public static int MaxRetryCount
{
get => _maxRetryCount;
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException(
nameof(value),
"再試行回数には0以上の値を指定してください。");
}
_maxRetryCount = value;
}
}
}
変更する必要がない値は、読み取り専用プロパティやconst、static readonlyを使用します。
C#public static class SystemConstants
{
public const int MaxUserNameLength = 50;
public static readonly DateTime ServiceStartDate =
new DateTime(2020, 1, 1);
public static string ServiceName { get; } = "Sample Service";
}
constはコンパイル時に決まる定数に使用します。static readonlyは、実行時に生成する値や、コンストラクターで初期化した後に変更させたくない値に適しています。
2-4. staticコンストラクターによる初期化方法
static classには、staticコンストラクターを定義できます。staticコンストラクターは、staticメンバーの初期化に使用する特別なコンストラクターです。
C#public static class Configuration
{
public static string EnvironmentName { get; }
static Configuration()
{
EnvironmentName =
Environment.GetEnvironmentVariable("APP_ENV")
?? "Development";
}
}
staticコンストラクターには、次の特徴があります。
staticを付けて定義するアクセス修飾子を付けられない
引数を指定できない
開発者が直接呼び出せない
型の初期化時に一度だけ実行される
次のように引数やpublicを付けることはできません。
C#public static class Configuration
{
// コンパイルエラー
public static Configuration(string environmentName)
{
}
}
正しい定義は次のとおりです。
C#public static class Configuration
{
public static string EnvironmentName { get; }
static Configuration()
{
EnvironmentName = "Production";
}
}
staticコンストラクターの実行タイミングは、一般的には型が初めて使用される前です。ただし、正確な初期化タイミングを前提とした処理は避け、初期化が一度だけ安全に行われる仕組みとして利用するのが基本です。
staticコンストラクター内で例外が発生すると、その型を正常に使用できなくなる可能性があります。そのため、ファイルアクセスやネットワーク通信など、失敗しやすい処理を無条件に実行する場合は注意が必要です。
2-5. 別の名前空間にあるstatic classを利用する方法
static classが別の名前空間に定義されている場合は、完全修飾名を使用するか、usingディレクティブを記述します。
次のstatic classがSampleApp.Utilities名前空間にあるとします。
C#namespace SampleApp.Utilities;
public static class TextHelper
{
public static string TrimAndLower(string value)
{
return value.Trim().ToLowerInvariant();
}
}
完全修飾名で呼び出す場合は、次のように記述します。
C#string result =
SampleApp.Utilities.TextHelper.TrimAndLower(" HELLO ");
Console.WriteLine(result); // hello
ファイルの先頭にusingを記述すれば、名前空間を省略できます。
C#using SampleApp.Utilities;
string result = TextHelper.TrimAndLower(" HELLO ");
同じ名前のクラスが複数の名前空間に存在する場合は、完全修飾名またはusingエイリアスを利用すると、どのクラスを使用しているか明確にできます。
C#using UtilityTextHelper = SampleApp.Utilities.TextHelper;
string result = UtilityTextHelper.TrimAndLower(" HELLO ");
2-6. using staticでクラス名を省略する方法
C#では、using staticを使用すると、static classのクラス名を省略してメンバーを呼び出せます。
通常は、次のようにクラス名を付けて呼び出します。
C#double result = Math.Sqrt(16);
Console.WriteLine(result);
using static System.Math;を記述すると、Mathを省略できます。
C#using static System.Math;
double result = Sqrt(16);
Console.WriteLine(result); // 4
独自に定義したstatic classにも使用できます。
C#namespace SampleApp.Utilities;
public static class NumberUtility
{
public static bool IsEven(int value)
{
return value % 2 == 0;
}
}
利用するファイルで次のように記述します。
C#using static SampleApp.Utilities.NumberUtility;
bool result = IsEven(10);
Console.WriteLine(result); // True
using staticは、数式処理など、同じstatic classのメンバーを何度も呼び出す場合に便利です。
一方で、クラス名を省略すると、そのメソッドがどこで定義されているのか分かりにくくなることがあります。複数のstatic classに同名のメソッドが存在すると、名前の競合も発生します。
可読性を優先する場合は、無理にusing staticを使わず、Math.Sqrt()のようにクラス名を明示する方法も検討しましょう。
3. static classと通常クラスの違い
3-1. インスタンスを生成できるかどうか
static classと通常クラスの最も大きな違いは、インスタンスを生成できるかどうかです。
通常クラスは、new演算子で複数のインスタンスを生成できます。
C#public class Counter
{
public int Value { get; private set; }
public void Increment()
{
Value++;
}
}
C#Counter counter1 = new Counter();
Counter counter2 = new Counter();
counter1.Increment();
counter1.Increment();
counter2.Increment();
Console.WriteLine(counter1.Value); // 2
Console.WriteLine(counter2.Value); // 1
counter1とcounter2は別々のインスタンスなので、それぞれ異なる状態を保持できます。
static classはインスタンスを生成できず、クラスに属するメンバーを直接利用します。
C#public static class GlobalCounter
{
public static int Value { get; private set; }
public static void Increment()
{
Value++;
}
}
C#GlobalCounter.Increment();
GlobalCounter.Increment();
Console.WriteLine(GlobalCounter.Value); // 2
GlobalCounter.Valueはアプリケーション内で共有される値です。利用箇所ごとに独立したカウンターを作ることはできません。
3-2. インスタンスメンバーを定義できるかどうか
通常クラスには、インスタンスメンバーとstaticメンバーの両方を定義できます。
C#public class Product
{
public static int CreatedCount { get; private set; }
public string Name { get; }
public Product(string name)
{
Name = name;
CreatedCount++;
}
public void ShowName()
{
Console.WriteLine(Name);
}
}
NameとShowName()は、個々のProductインスタンスに属するインスタンスメンバーです。CreatedCountは、Productクラス全体で共有されるstaticメンバーです。
static classでは、インスタンスメンバーを定義できません。
C#public static class ProductUtility
{
public static string NormalizeName(string name)
{
return name.Trim();
}
}
static classは、個別のオブジェクトに属するデータを扱うのではなく、クラス全体に属する処理を提供します。
3-3. 継承できるかどうか
通常クラスは、別のクラスを継承できます。また、sealedでない通常クラスは、ほかのクラスから継承されることもあります。
C#public class Animal
{
public void Eat()
{
Console.WriteLine("食べます。");
}
}
public class Dog : Animal
{
public void Bark()
{
Console.WriteLine("吠えます。");
}
}
static classは、ほかのクラスを継承できません。また、static classを基底クラスとして継承することもできません。
C#public static class BaseUtility
{
}
// コンパイルエラー
public class CustomUtility : BaseUtility
{
}
static classはインターフェースも実装できません。
そのため、継承やインターフェースを利用して処理を差し替えたい場合は、通常クラスを使用する必要があります。特に、依存性注入を利用するアプリケーションでは、インターフェースと通常クラスを組み合わせた設計が適しています。
3-4. コンストラクターの定義方法の違い
通常クラスには、インスタンスを初期化するためのコンストラクターを定義できます。
C#public class Customer
{
public string Name { get; }
public Customer(string name)
{
Name = name;
}
}
C#Customer customer = new Customer("山田");
通常クラスのコンストラクターには、アクセス修飾子や引数を指定できます。用途に応じて複数のコンストラクターを定義することも可能です。
static classには、インスタンスコンストラクターを定義できません。定義できるのは、staticメンバーを初期化するためのstaticコンストラクターだけです。
C#public static class AppContext
{
public static DateTime StartedAt { get; }
static AppContext()
{
StartedAt = DateTime.Now;
}
}
staticコンストラクターは引数を受け取れず、アクセス修飾子も指定できません。また、型ごとに1つだけ定義できます。
3-5. 状態の保持方法とライフサイクルの違い
通常クラスのインスタンスフィールドやインスタンスプロパティは、生成されたオブジェクトごとに状態を保持します。
C#public class ShoppingCart
{
public int ItemCount { get; private set; }
public void AddItem()
{
ItemCount++;
}
}
複数のShoppingCartを生成すると、それぞれ別の状態を持ちます。
C#ShoppingCart cart1 = new ShoppingCart();
ShoppingCart cart2 = new ShoppingCart();
cart1.AddItem();
cart1.AddItem();
cart2.AddItem();
Console.WriteLine(cart1.ItemCount); // 2
Console.WriteLine(cart2.ItemCount); // 1
インスタンスは、参照されなくなるとガベージコレクションの対象になります。
一方、static classのstaticフィールドやstaticプロパティは、型に対して1つだけ存在します。
C#public static class SharedData
{
public static int AccessCount { get; set; }
}
staticデータはアプリケーション内で広く共有され、一般的には、その型が読み込まれている期間にわたって保持されます。
そのため、大きなオブジェクトをstaticフィールドに保持すると、不要になっても長期間メモリに残る可能性があります。また、変更可能なstaticデータは複数の処理から同時にアクセスされる可能性があるため、スレッドセーフな設計が必要です。
3-6. static classと通常クラスの違いを比較表で確認
| 比較項目 | static class | 通常クラス |
|---|---|---|
| インスタンス化 | できない | できる |
| インスタンスメンバー | 定義できない | 定義できる |
| staticメンバー | 定義できる | 定義できる |
| インスタンスコンストラクター | 定義できない | 定義できる |
| staticコンストラクター | 定義できる | 定義できる |
| 継承元としての利用 | できない | sealedでなければ可能 |
| ほかのクラスの継承 | できない | 可能 |
| インターフェースの実装 | できない | 可能 |
| 状態の保持 | 型全体で共有 | インスタンスごとに保持 |
| 主な用途 | 共通処理、変換、定数、拡張メソッド | データと振る舞いを持つオブジェクト |
| テスト時の差し替え | 難しい | インターフェースなどで対応しやすい |
処理が完全にステートレスで、差し替えの必要がない場合はstatic classが便利です。利用箇所ごとに異なる状態が必要な場合や、テスト時に実装を差し替えたい場合は通常クラスが適しています。
4. static classとstaticメンバーの違い
4-1. 通常クラスにもstaticメンバーを定義できる
staticキーワードは、クラスそのものだけでなく、フィールド、プロパティ、メソッド、コンストラクターなどにも指定できます。
通常クラスにもstaticメンバーを定義できます。
C#public class Employee
{
public static int EmployeeCount { get; private set; }
public string Name { get; }
public Employee(string name)
{
Name = name;
EmployeeCount++;
}
}
Nameはインスタンスごとに異なる値を保持します。EmployeeCountはEmployeeクラス全体で共有されます。
C#Employee employee1 = new Employee("佐藤");
Employee employee2 = new Employee("鈴木");
Console.WriteLine(employee1.Name); // 佐藤
Console.WriteLine(employee2.Name); // 鈴木
Console.WriteLine(Employee.EmployeeCount); // 2
このように、通常クラスは「インスタンスごとのメンバー」と「クラス全体で共有するstaticメンバー」を組み合わせられます。
4-2. static classではインスタンスメンバーを定義できない
static classは、クラス全体が静的な機能の入れ物です。そのため、インスタンスメンバーを定義できません。
C#public static class StringUtility
{
public static bool IsEmpty(string? value)
{
return string.IsNullOrEmpty(value);
}
}
一方、通常クラスではstaticメンバーとインスタンスメンバーを併用できます。
C#public class Temperature
{
public double Celsius { get; }
public Temperature(double celsius)
{
Celsius = celsius;
}
public double ToFahrenheit()
{
return Celsius * 9 / 5 + 32;
}
public static double CelsiusToFahrenheit(double celsius)
{
return celsius * 9 / 5 + 32;
}
}
インスタンスメソッドは、インスタンスが保持しているCelsiusを使用します。
C#Temperature temperature = new Temperature(20);
Console.WriteLine(temperature.ToFahrenheit()); // 68
staticメソッドは、引数で受け取った値を使用します。
C#double result = Temperature.CelsiusToFahrenheit(20);
Console.WriteLine(result); // 68
4-3. 通常クラスでstaticメンバーを使うサンプルコード
通常クラスでstaticメンバーを使用する例として、生成されたインスタンス数を記録するクラスを考えます。
C#public class User
{
public static int CreatedCount { get; private set; }
public int Id { get; }
public string Name { get; }
public User(int id, string name)
{
Id = id;
Name = name;
CreatedCount++;
}
public void ShowProfile()
{
Console.WriteLine($"{Id}: {Name}");
}
public static void ShowCreatedCount()
{
Console.WriteLine($"生成数: {CreatedCount}");
}
}
呼び出し側では、インスタンスメンバーとstaticメンバーでアクセス方法が異なります。
C#User user1 = new User(1, "高橋");
User user2 = new User(2, "伊藤");
user1.ShowProfile();
user2.ShowProfile();
User.ShowCreatedCount();
実行結果は次のとおりです。
1: 高橋
2: 伊藤
生成数: 2
ShowProfile()は特定のUserインスタンスに対して呼び出します。ShowCreatedCount()はクラス全体に関係する処理なので、User.ShowCreatedCount()と記述します。
staticメンバーをインスタンス経由で呼び出すのではなく、クラス名を使ってアクセスすることで、共有メンバーであることが明確になります。
4-4. static classとstaticメンバーを使い分ける基準
static classと通常クラス内のstaticメンバーは、次の基準で使い分けられます。
クラス内のすべての処理がインスタンスの状態に依存せず、今後もインスタンス化する必要がない場合は、static classが適しています。
C#public static class UnitConverter
{
public static double KilometersToMiles(double kilometers)
{
return kilometers * 0.621371;
}
}
一方、クラスが個別の状態を持ちつつ、一部の情報だけをクラス全体で共有する場合は、通常クラスにstaticメンバーを定義します。
C#public class Account
{
public static int AccountCount { get; private set; }
public string OwnerName { get; }
public decimal Balance { get; private set; }
public Account(string ownerName)
{
OwnerName = ownerName;
AccountCount++;
}
public void Deposit(decimal amount)
{
Balance += amount;
}
}
将来的に継承、インターフェース、依存性注入、モックへの差し替えが必要になる可能性が高い場合は、static classより通常クラスが適しています。
特に、データベース、現在日時、ファイル、ネットワーク、外部APIなどに依存する処理をstatic classへ直接実装すると、テストで差し替えにくくなります。
例えば、次の設計では現在日時を固定しにくいため、テストが複雑になります。
C#public static class CampaignChecker
{
public static bool IsActive(DateTime start, DateTime end)
{
DateTime now = DateTime.Now;
return start <= now && now <= end;
}
}
現在日時を引数として受け取れば、staticメソッドのままでもテストしやすくなります。
C#public static class CampaignChecker
{
public static bool IsActive(
DateTime start,
DateTime end,
DateTime current)
{
return start <= current && current <= end;
}
}
C#bool result = CampaignChecker.IsActive(
new DateTime(2026, 1, 1),
new DateTime(2026, 12, 31),
new DateTime(2026, 6, 1));
static classを使用する場合も、外部環境へ直接依存せず、引数と戻り値が明確な設計にすると再利用性とテスト容易性が高まります。
5. C#でstatic classを活用する実践例
static classが特に適しているのは、入力値だけで結果が決まり、内部に変更可能な状態を持たない処理です。
ここでは、実践的な活用例として、文字列の検証処理をまとめたstatic classを作成します。
C#public static class ValidationHelper
{
public static bool IsRequired(string? value)
{
return !string.IsNullOrWhiteSpace(value);
}
public static bool IsWithinLength(
string? value,
int maxLength)
{
if (value is null)
{
return true;
}
return value.Length <= maxLength;
}
public static bool IsPositive(int value)
{
return value > 0;
}
}
呼び出し側では、次のように使用できます。
C#string? userName = "山田太郎";
int age = 25;
if (!ValidationHelper.IsRequired(userName))
{
Console.WriteLine("名前は必須です。");
}
if (!ValidationHelper.IsWithinLength(userName, 50))
{
Console.WriteLine("名前は50文字以内で入力してください。");
}
if (!ValidationHelper.IsPositive(age))
{
Console.WriteLine("年齢には1以上の値を入力してください。");
}
このstatic classは変更可能なstaticフィールドを持たず、渡された引数だけを使って判定しています。そのため、呼び出し結果が予測しやすく、単体テストも容易です。
C#bool result1 = ValidationHelper.IsRequired("C#");
bool result2 = ValidationHelper.IsRequired(" ");
bool result3 = ValidationHelper.IsPositive(10);
Console.WriteLine(result1); // True
Console.WriteLine(result2); // False
Console.WriteLine(result3); // True
もう一つの実践例として、金額表示を行うstatic classを作成します。
C#using System.Globalization;
public static class CurrencyFormatter
{
public static string ToJapaneseYen(decimal amount)
{
CultureInfo culture = CultureInfo.GetCultureInfo("ja-JP");
return amount.ToString("C0", culture);
}
}
C#string formattedPrice =
CurrencyFormatter.ToJapaneseYen(12800m);
Console.WriteLine(formattedPrice);
static classは拡張メソッドの定義にも使用されます。拡張メソッドを定義するクラスは、static classでなければなりません。
C#public static class StringExtensions
{
public static bool IsNullOrBlank(this string? value)
{
return string.IsNullOrWhiteSpace(value);
}
}
定義した拡張メソッドは、文字列のインスタンスメソッドのように呼び出せます。
C#string? input = " ";
if (input.IsNullOrBlank())
{
Console.WriteLine("文字列が入力されていません。");
}
ただし、static classで変更可能な状態を共有するときは注意が必要です。
次のカウンターは、複数のスレッドから同時に呼び出されると、正しく加算されない可能性があります。
C#public static class AccessCounter
{
public static int Count { get; private set; }
public static void Increment()
{
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);
}
}
static classを実践で活用するときは、次の点を意識することが重要です。
可能な限り変更可能なstaticフィールドを持たせない
入力を引数で受け取り、結果を戻り値で返す
クラスの責務を限定する
外部環境への依存を増やしすぎない
共有状態を変更する場合は並列実行を考慮する
テスト時に差し替えが必要な処理は通常クラスを検討する
static classは便利ですが、あらゆる共通処理を1つのクラスへ集めるための仕組みではありません。関連する処理だけをまとめ、クラス名から役割を判断できる設計にしましょう。
まとめ
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);
static classには、インスタンス化できない、インスタンスメンバーを定義できない、継承できないという特徴があります。通常クラスは個々のインスタンスが異なる状態を持てますが、static classのデータは型全体で共有されます。
数値計算、文字列変換、入力値の検証、定数、拡張メソッドなど、オブジェクトごとの状態を必要としない処理にはstatic classが適しています。一方、インスタンスごとの状態、継承、インターフェース、依存性注入、テスト時の差し替えが必要な場合は、通常クラスを選ぶべきです。
また、変更可能なstaticフィールドは、予期しない値の変更、スレッド競合、テスト間の状態共有、長期間のメモリ保持といった問題を引き起こす可能性があります。static classを使用するときは、できるだけ状態を持たない設計にし、引数と戻り値が明確な処理をまとめることが重要です。

