C#のstaticとは?初心者がつまずく意味・使い方・使うべき場面をサンプルコードで徹底解説

はじめに

C#を学び始めると、メソッドやフィールドの前に付いているstaticというキーワードを頻繁に見かけます。

C#
public static void Main()
{
Console.WriteLine("Hello, C#");
}

初心者にとっては、「なぜMainにはstaticが付いているのか」「付ける場合と付けない場合で何が変わるのか」が分かりにくいポイントです。

C#のstaticを簡単に表すと、インスタンスを作らなくても、クラス名から直接利用できるメンバーを定義するためのキーワードです。ただし、何でもstaticにすればよいわけではありません。staticメンバーはアプリケーション内で共有されるため、使い方を誤ると、値が意図せず書き換わったり、テストしにくくなったりする可能性があります。

この記事では、C#のstaticの意味、基本構文、通常のメンバーとの違い、使うべき場面、注意点、よくあるエラーをサンプルコード付きで解説します。

1. C#のstaticとは?まずは意味を初心者向けに解説

1-1. staticは「インスタンスを作らずに使える」メンバーを表すキーワード

C#のstaticは、メソッドやフィールドなどをクラスそのものに所属させるためのキーワードです。

通常のメソッドを利用するには、newを使ってクラスのインスタンスを作る必要があります。

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

一方、staticメソッドはインスタンスを作らず、クラス名から直接呼び出せます。

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

この違いが、C#のstaticを理解するうえで最も重要なポイントです。

1-2. staticが付いたメソッド・変数はクラスそのものに所属する

通常のフィールドやメソッドは、作成したインスタンスごとに存在します。

これに対して、staticが付いたフィールドやメソッドは、個々のインスタンスではなくクラスそのものに所属します。

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

public static int UserCount { get; set; }
}

この例では、Nameはユーザーごとに異なる値を持つインスタンスプロパティです。一方、UserCountはすべてのユーザーで共有されるstaticプロパティです。

C#
var user1 = new User { Name = "田中" };
var user2 = new User { Name = "佐藤" };

User.UserCount = 2;

Console.WriteLine(user1.Name); // 田中
Console.WriteLine(user2.Name); // 佐藤
Console.WriteLine(User.UserCount); // 2

Nameは各インスタンスに所属し、UserCountUserクラスそのものに所属していると考えると理解しやすいでしょう。

1-3. staticを理解する前に押さえたい「クラス」と「インスタンス」の違い

クラスは、オブジェクトを作るための設計図です。インスタンスは、その設計図をもとに実際に作られたオブジェクトです。

たとえば、次のCarクラスは車の設計図です。

C#
public class Car
{
public string Color { get; set; } = "";

public void Drive()
{
Console.WriteLine($"{Color}の車が走ります");
}
}

new Car()を実行すると、Carクラスのインスタンスが作られます。

C#
var redCar = new Car { Color = "赤" };
var blueCar = new Car { Color = "青" };

redCar.Drive();
blueCar.Drive();

実行結果は次のとおりです。

赤の車が走ります
青の車が走ります

Colorは車ごとに異なるため、インスタンスメンバーにするのが自然です。

一方、「作成された車の総数」のように、すべての車に共通する情報はstaticメンバーとして定義できます。

C#
public class Car
{
public static int TotalCount { get; private set; }

public Car()
{
TotalCount++;
}
}
C#
var car1 = new Car();
var car2 = new Car();

Console.WriteLine(Car.TotalCount); // 2

1-4. staticの基本構文と最小サンプルコード

staticメソッドは、戻り値の型より前にstaticを付けて定義します。

C#
アクセス修飾子 static 戻り値の型 メソッド名()
{
// 処理
}

最小構成のサンプルは次のとおりです。

C#
public class Message
{
public static void Show()
{
Console.WriteLine("staticメソッドが呼び出されました");
}
}

呼び出すときは、クラス名.メソッド名()と記述します。

C#
Message.Show();

new Message()によるインスタンス化は必要ありません。

2. staticなしとの違いをサンプルコードで比較

2-1. 通常のメソッドはnewでインスタンス化してから呼び出す

staticが付いていないメソッドはインスタンスメソッドです。

C#
public class Greeter
{
public void Greet(string name)
{
Console.WriteLine($"こんにちは、{name}さん");
}
}

インスタンスメソッドを呼び出すには、最初にインスタンスを作ります。

C#
var greeter = new Greeter();
greeter.Greet("田中");

インスタンスメソッドは、インスタンスごとに保持しているフィールドやプロパティを利用できます。

C#
public class Greeter
{
public string Prefix { get; set; } = "こんにちは";

public void Greet(string name)
{
Console.WriteLine($"{Prefix}、{name}さん");
}
}
C#
var morningGreeter = new Greeter
{
Prefix = "おはようございます"
};

var eveningGreeter = new Greeter
{
Prefix = "こんばんは"
};

morningGreeter.Greet("田中");
eveningGreeter.Greet("佐藤");

オブジェクトごとに異なる状態を扱う処理には、インスタンスメソッドが適しています。

2-2. staticメソッドはクラス名から直接呼び出せる

次のようにstaticを付けたメソッドは、クラス名から直接呼び出せます。

C#
public class NumberHelper
{
public static bool IsEven(int number)
{
return number % 2 == 0;
}
}
C#
bool result = NumberHelper.IsEven(10);

Console.WriteLine(result); // True

IsEvenメソッドは、渡された数値だけを使って結果を返しています。特定のインスタンスが持つ状態に依存していないため、staticメソッドに適した処理です。

2-3. staticフィールドはすべてのインスタンスで共有される

staticフィールドは、クラスから作られたすべてのインスタンスで共有されます。

C#
public class Player
{
public static int TotalScore = 0;

public void AddScore(int score)
{
TotalScore += score;
}
}
C#
var player1 = new Player();
var player2 = new Player();

player1.AddScore(10);
player2.AddScore(20);

Console.WriteLine(Player.TotalScore); // 30

player1player2は別々のインスタンスですが、TotalScoreは同じ領域を参照しています。

一方、staticを付けない場合は、インスタンスごとに別の値を持ちます。

C#
public class Player
{
public int Score = 0;
}
C#
var player1 = new Player();
var player2 = new Player();

player1.Score = 10;
player2.Score = 20;

Console.WriteLine(player1.Score); // 10
Console.WriteLine(player2.Score); // 20

2-4. インスタンスメンバーとstaticメンバーの使い分け早見表

比較項目インスタンスメンバーstaticメンバー
所属先作成したオブジェクトクラスそのもの
呼び出し方変数名.メンバー名クラス名.メンバー名
newの必要性必要不要
値の保持インスタンスごと全インスタンスで共有
インスタンスメンバーへのアクセス可能オブジェクト参照なしでは不可
主な用途ユーザー、商品、注文など個別の状態共通処理、共有値、定数、便利関数

判断に迷った場合は、「その値や処理がオブジェクトごとに変わるか」を考えます。オブジェクトごとに変わるならインスタンスメンバー、全体で共通ならstaticメンバーが候補になります。

3. C#でstaticを使える主な対象

3-1. staticメソッド:共通処理や便利関数に使う

staticメソッドは、特定のインスタンスの状態に依存しない共通処理に向いています。

C#
public static int Square(int number)
{
return number * number;
}

たとえば、計算、文字列変換、日付の整形、入力値の検証などが代表例です。

C#
public static string NormalizeName(string name)
{
return name.Trim().ToUpperInvariant();
}

3-2. staticフィールド:共有データを保持する

staticフィールドは、クラス全体で共有するデータを保持します。

C#
public class AccessCounter
{
public static int Count;
}
C#
AccessCounter.Count++;
Console.WriteLine(AccessCounter.Count);

ただし、外部から自由に変更できるpublic staticフィールドは、予期しない書き換えの原因になります。必要に応じてprivateフィールドとプロパティを組み合わせましょう。

C#
public class AccessCounter
{
private static int _count;

public static int Count => _count;

public static void Increment()
{
_count++;
}
}

3-3. staticプロパティ:共有値を安全に扱う

staticプロパティを使うと、共有値へのアクセスを制御できます。

C#
public class ApplicationState
{
public static string CurrentEnvironment { get; private set; } = "Development";

public static void ChangeEnvironment(string environment)
{
if (string.IsNullOrWhiteSpace(environment))
{
throw new ArgumentException("環境名を入力してください");
}

CurrentEnvironment = environment;
}
}
C#
Console.WriteLine(ApplicationState.CurrentEnvironment);

ApplicationState.ChangeEnvironment("Production");

Console.WriteLine(ApplicationState.CurrentEnvironment);

プロパティを使えば、値の検証や変更制限を加えられます。

3-4. staticクラス:インスタンス化しないクラスを作る

クラス自体にstaticを付けると、staticクラスになります。

C#
public static class TemperatureConverter
{
public static double CelsiusToFahrenheit(double celsius)
{
return celsius * 9 / 5 + 32;
}
}
C#
double fahrenheit = TemperatureConverter.CelsiusToFahrenheit(25);

Console.WriteLine(fahrenheit); // 77

staticクラスには、原則としてstaticメンバーだけを定義します。また、newを使ったインスタンス化はできません。

C#
// コンパイルエラー
var converter = new TemperatureConverter();

3-5. staticコンストラクター:初回利用時に一度だけ初期化する

staticコンストラクターは、クラスの静的な初期化を行うための特別なコンストラクターです。

C#
public class SystemSettings
{
public static string ApplicationName { get; }

static SystemSettings()
{
ApplicationName = "Sample Application";
Console.WriteLine("staticコンストラクターが実行されました");
}
}

staticコンストラクターには、次の特徴があります。

  • アクセス修飾子を付けない

  • 引数を指定できない

  • 開発者が直接呼び出すことはできない

  • 型の初期化時に一度だけ実行される

C#
Console.WriteLine(SystemSettings.ApplicationName);
Console.WriteLine(SystemSettings.ApplicationName);

同じ型の初期化処理として、staticコンストラクターが何度も実行されることはありません。

ただし、外部ファイルの読み込みやネットワーク通信など、失敗しやすい重い処理をstaticコンストラクターに入れる場合は注意が必要です。初期化に失敗すると、その型を利用できなくなる可能性があります。

3-6. using static:クラス名を省略してstaticメンバーを呼び出す

using staticを使うと、指定したクラスのstaticメンバーをクラス名なしで呼び出せます。

C#
using static System.Math;

double result = Sqrt(25);
Console.WriteLine(result); // 5

通常は次のように記述します。

C#
double result = Math.Sqrt(25);

using static System.Math;を記述すると、Math.を省略してSqrtRoundなどを呼び出せます。

C#
using static System.Math;

double value = Pow(2, 3);
double rounded = Round(3.14159, 2);

Console.WriteLine(value); // 8
Console.WriteLine(rounded); // 3.14

クラス名を省略しすぎると、どのクラスのメソッドなのか分かりにくくなることがあります。可読性を確認しながら利用しましょう。

4. staticメソッドの使い方を具体例で解説

4-1. 計算処理をstaticメソッドにする例

特定のオブジェクトの状態を使わない計算処理は、staticメソッドに向いています。

C#
public static class PriceCalculator
{
public static decimal CalculateTax(decimal price, decimal taxRate)
{
return price * taxRate;
}

public static decimal CalculateTotal(decimal price, decimal taxRate)
{
return price + CalculateTax(price, taxRate);
}
}
C#
decimal tax = PriceCalculator.CalculateTax(1000m, 0.1m);
decimal total = PriceCalculator.CalculateTotal(1000m, 0.1m);

Console.WriteLine(tax); // 100.0
Console.WriteLine(total); // 1100.0

入力された値だけで結果が決まるため、インスタンスを作る必要がありません。

4-2. 文字列変換やバリデーションをstaticメソッドにする例

文字列の変換処理も、staticメソッドの代表的な用途です。

C#
public static class StringHelper
{
public static string RemoveSpaces(string value)
{
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}

return value.Replace(" ", "").Replace(" ", "");
}
}
C#
string result = StringHelper.RemoveSpaces("C# static 入門");

Console.WriteLine(result); // C#static入門

入力チェックも、対象の値だけで判定できる場合はstaticメソッドにできます。

C#
public static class InputValidator
{
public static bool IsValidAge(int age)
{
return age >= 0 && age <= 150;
}

public static bool IsRequired(string? value)
{
return !string.IsNullOrWhiteSpace(value);
}
}
C#
Console.WriteLine(InputValidator.IsValidAge(25)); // True
Console.WriteLine(InputValidator.IsRequired("")); // False

4-3. Mainメソッドがstaticである理由

C#の一般的なコンソールアプリケーションでは、プログラムの開始地点としてMainメソッドを定義します。

C#
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("プログラムを開始します");
}
}

Mainstaticである理由は、プログラム開始時点ではProgramクラスのインスタンスが作られていないからです。

実行環境は、最初に呼び出すべきエントリーポイントを必要とします。Mainがインスタンスメソッドだった場合、呼び出す前にどのようにProgramオブジェクトを作るかという問題が生じます。

staticメソッドであれば、インスタンスが存在しなくてもクラスから直接呼び出せます。

なお、トップレベルステートメントを使う新しい形式では、ソースコード上にMainを書かない場合があります。

C#
Console.WriteLine("Hello, World!");

この場合も、コンパイラーがエントリーポイントに相当する処理を生成します。

4-4. staticメソッドからインスタンスメンバーを直接使えない理由

次のコードはコンパイルエラーになります。

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

public static void ShowName()
{
Console.WriteLine(Name);
}
}

Nameはインスタンスごとに異なる値を持ちます。しかし、ShowNamestaticメソッドなので、特定のUserインスタンスに所属していません。

たとえば、次の2人が存在するとします。

C#
var user1 = new User { Name = "田中" };
var user2 = new User { Name = "佐藤" };

User.ShowName()と呼び出しても、user1user2のどちらのNameを表示すべきか判断できません。そのため、staticメソッドからインスタンスメンバーを直接参照することは禁止されています。

4-5. staticメソッド内でインスタンスメンバーを使う方法

staticメソッド内でインスタンスメンバーを使うには、対象となるインスタンスを明示的に用意します。

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);

staticメソッドの中でインスタンスを作ることもできます。

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

public void Introduce()
{
Console.WriteLine($"私は{Name}です");
}

public static void CreateAndIntroduce()
{
var user = new User
{
Name = "田中"
};

user.Introduce();
}
}

ただし、その処理が特定のインスタンス状態を中心に動くのであれば、最初からインスタンスメソッドとして設計する方が自然な場合があります。

5. static変数・staticフィールドの使い方と注意点

5-1. staticフィールドは値が共有される

C#で一般に「static変数」と呼ばれているものは、多くの場合、クラスに定義したstaticフィールドを指します。

C#
public class Game
{
public static int HighScore;
}

このフィールドは、すべてのGameインスタンスで共有されます。

C#
var game1 = new Game();
var game2 = new Game();

Game.HighScore = 100;

Console.WriteLine(Game.HighScore); // 100

C#には、C言語やC++のようにメソッド内のローカル変数へstaticを付ける構文はありません。

C#
public void Sample()
{
// C#では使用できない
// static int count = 0;
}

メソッドをまたいで値を保持したい場合は、クラスのstaticフィールドなどを利用します。ただし、本当にアプリケーション全体で共有すべき値なのかを確認する必要があります。

5-2. カウンター機能をstaticフィールドで作る例

作成されたインスタンス数を数えるカウンターを作ってみましょう。

C#
public class Customer
{
private static int _createdCount;

public static int CreatedCount => _createdCount;

public Customer()
{
_createdCount++;
}
}
C#
var customer1 = new Customer();
var customer2 = new Customer();
var customer3 = new Customer();

Console.WriteLine(Customer.CreatedCount); // 3

_createdCountはすべてのインスタンスで共有されるため、新しいCustomerが作られるたびに同じ値が増加します。

外部から勝手に値を変更されないように、フィールドをprivateにして、読み取り専用のプロパティで公開しています。

5-3. const・readonly・static readonlyの違い

C#で変更されない値を定義するときは、constreadonlystatic readonlyを使い分けます。

C#
public class Sample
{
public const decimal TaxRate = 0.1m;

public readonly DateTime CreatedAt;

public static readonly string ApplicationName = "SampleApp";

public Sample()
{
CreatedAt = DateTime.Now;
}
}

それぞれの違いは次のとおりです。

キーワード値を設定できるタイミング共有範囲主な用途
constコンパイル時型全体で共有完全に固定された定数
readonly宣言時またはインスタンスコンストラクターインスタンスごと作成後に変更しない値
static readonly宣言時またはstaticコンストラクター型全体で共有実行時に決まる共有の読み取り専用値

constは暗黙的にstaticとして扱われるため、static constとは記述しません。

C#
public class AppConstants
{
public const int MaxRetryCount = 3;
}
C#
Console.WriteLine(AppConstants.MaxRetryCount);

実行時に計算する値や、コンストラクターで初期化したい共有値にはstatic readonlyを使います。

C#
public class AppInfo
{
public static readonly DateTime StartedAt = DateTime.Now;
}

5-4. static変数を使いすぎるとバグの原因になる理由

変更可能なstaticフィールドは、アプリケーションのさまざまな場所から参照・変更される可能性があります。

C#
public static class GlobalState
{
public static string CurrentUserName = "";
}
C#
GlobalState.CurrentUserName = "田中";

// 別の処理で書き換えられる
GlobalState.CurrentUserName = "佐藤";

どこからでも変更できる共有状態が増えると、次の問題が起こりやすくなります。

  • どの処理が値を変更したのか追跡しにくい

  • 処理の実行順序によって結果が変わる

  • テスト同士が影響し合う

  • 複数ユーザーの情報が混ざる

  • 並行処理で値が壊れる

特にWebアプリケーションでは、staticフィールドが複数のリクエストで共有されることがあります。ユーザー固有の情報をstaticフィールドに保存してはいけません。

悪い例は次のとおりです。

C#
public static class CurrentUser
{
public static string UserName = "";
}

複数ユーザーが同時にアクセスすると、あるユーザーの名前が別のユーザーの値で上書きされる危険があります。

5-5. マルチスレッド環境でstaticを使うときの注意点

複数のスレッドから変更可能なstaticフィールドへ同時にアクセスすると、競合状態が発生することがあります。

C#
public static class Counter
{
public static int Value;

public static void Increment()
{
Value++;
}
}

Value++は、内部的には値の読み取り、加算、書き込みという複数の処理を含みます。複数スレッドが同時に実行すると、増加分が失われる可能性があります。

単純な整数カウンターであれば、Interlockedを利用できます。

C#
using System.Threading;

public static class Counter
{
private static int _value;

public static int Value => Volatile.Read(ref _value);

public static void Increment()
{
Interlocked.Increment(ref _value);
}
}

複数の処理をまとめて排他的に実行したい場合は、lockを使います。

C#
public static class SharedList
{
private static readonly object _lockObject = new();
private static readonly List<string> _items = new();

public static void Add(string item)
{
lock (_lockObject)
{
_items.Add(item);
}
}

public static IReadOnlyList<string> GetItems()
{
lock (_lockObject)
{
return _items.ToList();
}
}
}

共有状態を安全に扱う設計は難しいため、変更可能なstaticデータを作る前に、本当に共有が必要かを検討しましょう。

6. staticクラスの使い方と使うべき場面

6-1. staticクラスはインスタンス化できない

staticクラスは、インスタンスを作らずに使用することを前提としたクラスです。

C#
public static class MessageHelper
{
public static string CreateWelcomeMessage(string name)
{
return $"ようこそ、{name}さん";
}
}

次のようなインスタンス化はできません。

C#
// コンパイルエラー
var helper = new MessageHelper();

呼び出すときはクラス名を使います。

C#
string message = MessageHelper.CreateWelcomeMessage("田中");

Console.WriteLine(message);

また、staticクラスを通常のクラスの基底クラスにしたり、staticクラスから別のクラスを継承したりすることもできません。

6-2. ユーティリティクラスをstaticクラスで作る例

複数の場所で使う共通処理をまとめたクラスは、ユーティリティクラスやヘルパークラスと呼ばれます。

C#
public static class DateTimeHelper
{
public static string ToJapaneseDate(DateTime date)
{
return date.ToString("yyyy年MM月dd日");
}

public static bool IsWeekend(DateTime date)
{
return date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday;
}
}
C#
DateTime date = new DateTime(2026, 6, 20);

Console.WriteLine(DateTimeHelper.ToJapaneseDate(date));
Console.WriteLine(DateTimeHelper.IsWeekend(date));

このクラスは状態を保持せず、引数から結果を計算するだけなので、staticクラスに適しています。

6-3. 拡張メソッドを作るときのstaticクラス

C#の拡張メソッドは、staticクラス内のstaticメソッドとして定義します。

C#
public static class StringExtensions
{
public static bool IsNullOrEmptyOrWhiteSpace(this string? value)
{
return string.IsNullOrWhiteSpace(value);
}
}

次のように、既存の型へメソッドを追加したような形で呼び出せます。

C#
string? name = "  ";

bool result = name.IsNullOrEmptyOrWhiteSpace();

Console.WriteLine(result); // True

拡張メソッドには、次の条件があります。

  • 非ジェネリックのstaticクラス内に定義する

  • メソッド自体をstaticにする

  • 最初の引数へthisを付ける

別の例として、数値用の拡張メソッドを作れます。

C#
public static class IntExtensions
{
public static bool IsPositive(this int value)
{
return value > 0;
}
}
C#
int number = 10;

Console.WriteLine(number.IsPositive()); // True

6-4. staticクラスに向いている処理・向いていない処理

staticクラスに向いているのは、次のような処理です。

  • 入力値だけで結果が決まる計算処理

  • 文字列や日付の変換

  • 入力チェック

  • 定数の管理

  • 拡張メソッド

  • 状態を持たない共通処理

反対に、次のような処理には慎重な判断が必要です。

  • ユーザーごとに状態が変わる処理

  • データベースへアクセスする処理

  • 外部APIを呼び出す処理

  • 実行環境によって動作を差し替えたい処理

  • モックを使って単体テストしたい処理

  • 生成から破棄までのライフサイクル管理が必要な処理

たとえば、固定的な文字列変換はstaticクラスに向いています。

C#
public static class TextConverter
{
public static string ToUpper(string value)
{
return value.ToUpperInvariant();
}
}

一方、データベースからユーザーを取得する処理は、依存関係を差し替えられる通常クラスの方が適していることがあります。

C#
public class UserService
{
private readonly IUserRepository _repository;

public UserService(IUserRepository repository)
{
_repository = repository;
}

public User? FindById(int id)
{
return _repository.FindById(id);
}
}

6-5. staticクラスと通常クラスの設計上の違い

staticクラスは、アプリケーション内に事実上1つしかない共通機能として利用されます。呼び出しは簡単ですが、実装の差し替えや継承には向いていません。

通常クラスは、インスタンスごとに状態を持てます。また、インターフェースを利用すれば実装を差し替えられます。

C#
public interface IMessageSender
{
void Send(string message);
}

public class EmailMessageSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine($"メール送信: {message}");
}
}

テスト時には、別の実装へ差し替えられます。

C#
public class FakeMessageSender : IMessageSender
{
public List<string> SentMessages { get; } = new();

public void Send(string message)
{
SentMessages.Add(message);
}
}

一方、staticメソッドを直接呼び出す設計では、このような差し替えが難しくなります。

単純な便利関数にはstaticクラス、外部システムとの連携や振る舞いの切り替えが必要な処理には通常クラス、という考え方が有効です。

7. staticを使うべき場面・使わない方がよい場面

7-1. 状態を持たない共通処理にはstaticが向いている

引数だけで結果が決まり、外部の状態を変更しない処理はstaticに向いています。

C#
public static class AreaCalculator
{
public static double CalculateRectangle(double width, double height)
{
return width * height;
}
}

この処理は、同じ引数を渡せば同じ結果になります。インスタンスごとに保持すべき状態もありません。

C#
double area = AreaCalculator.CalculateRectangle(10, 5);

Console.WriteLine(area); // 50

このような処理は挙動が分かりやすく、staticにしても問題が起こりにくい傾向があります。

7-2. アプリ全体で共有する定数や設定値に使う

アプリケーション全体で共通する定数は、conststatic readonlyで管理できます。

C#
public static class ApplicationConstants
{
public const int MaxLoginAttempts = 5;

public static readonly TimeSpan DefaultTimeout =
TimeSpan.FromSeconds(30);
}
C#
Console.WriteLine(ApplicationConstants.MaxLoginAttempts);
Console.WriteLine(ApplicationConstants.DefaultTimeout);

ただし、環境ごとに変わる接続文字列や秘密情報をソースコードへ直接書くのは避けましょう。そのような値は設定ファイル、環境変数、シークレット管理サービスなどから取得するのが一般的です。

7-3. オブジェクトごとに値が変わるものにはstaticを使わない

社員ごとの名前や給与、商品の価格、注文ごとの合計金額などは、インスタンスごとに異なります。

次の設計は不適切です。

C#
public class Employee
{
public static string Name = "";
public static decimal Salary;
}

複数の社員を作っても、値がすべて共有されてしまいます。

C#
Employee.Name = "田中";
Employee.Salary = 300000m;

Employee.Name = "佐藤";
Employee.Salary = 400000m;

Console.WriteLine(Employee.Name); // 佐藤

社員ごとの値はインスタンスメンバーにします。

C#
public class Employee
{
public string Name { get; set; } = "";
public decimal Salary { get; set; }
}
C#
var employee1 = new Employee
{
Name = "田中",
Salary = 300000m
};

var employee2 = new Employee
{
Name = "佐藤",
Salary = 400000m
};

7-4. テストしやすさを重視する場合はstaticを避けることもある

単純な計算処理であれば、staticメソッドでも容易にテストできます。

C#
public static int Add(int left, int right)
{
return left + right;
}

一方、現在時刻、ファイル、データベース、ネットワークなどに依存する処理をstaticメソッドとして直接呼び出すと、テスト時に差し替えにくくなります。

たとえば、次の処理は現在時刻へ直接依存しています。

C#
public static class GreetingService
{
public static string CreateGreeting()
{
return DateTime.Now.Hour < 12
? "おはようございます"
: "こんにちは";
}
}

テスト実行時刻によって結果が変わるため、テストしにくい設計です。

時刻を取得する機能をインターフェースとして分離すると、テストしやすくなります。

C#
public interface IClock
{
DateTime Now { get; }
}

public class SystemClock : IClock
{
public DateTime Now => DateTime.Now;
}

public class GreetingService
{
private readonly IClock _clock;

public GreetingService(IClock clock)
{
_clock = clock;
}

public string CreateGreeting()
{
return _clock.Now.Hour < 12
? "おはようございます"
: "こんにちは";
}
}

7-5. staticにするか迷ったときの判断基準

staticにするか迷ったら、次の点を確認します。

  1. インスタンスごとの状態を使うか

  2. その処理は入力値だけで結果が決まるか

  3. 複数の場所で共有すべき値か

  4. 将来、実装を差し替える可能性があるか

  5. 単体テストでモック化する必要があるか

  6. 複数スレッドから同時に利用されるか

  7. 変更可能なグローバル状態を作っていないか

状態を持たない単純な処理で、差し替えの必要もないならstaticが有力です。

オブジェクトごとに状態が異なる、依存関係を差し替えたい、ライフサイクルを管理したいという場合は、通常クラスを検討しましょう。

8. 初心者がつまずきやすいstaticのエラーと解決方法

8-1. 「静的でないフィールド、メソッド、またはプロパティでオブジェクト参照が必要です」の原因

次のように、staticメソッドからインスタンスメンバーを直接呼び出すと、オブジェクト参照が必要であることを示すコンパイルエラーが発生します。

C#
public class Sample
{
public string Message { get; set; } = "Hello";

public void ShowMessage()
{
Console.WriteLine(Message);
}

public static void Execute()
{
ShowMessage();
}
}

Executeはクラスに所属していますが、ShowMessageはインスタンスに所属しています。どのインスタンスのShowMessageを呼ぶのか分からないため、直接呼び出せません。

対象のインスタンスを作れば呼び出せます。

C#
public static void Execute()
{
var sample = new Sample();
sample.ShowMessage();
}

または、処理の目的に応じてExecute自体をインスタンスメソッドに変更します。

C#
public void Execute()
{
ShowMessage();
}

8-2. staticメソッドからthisが使えない理由

thisは、現在のインスタンス自身を表すキーワードです。

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

public void ShowName()
{
Console.WriteLine(this.Name);
}
}

staticメソッドには現在のインスタンスが存在しないため、thisは使用できません。

C#
public static void Show()
{
// コンパイルエラー
// Console.WriteLine(this);
}

対象のインスタンスが必要なら、引数として受け取ります。

C#
public static void Show(User user)
{
Console.WriteLine(user.Name);
}

8-3. staticクラスをnewできない理由

staticクラスは、インスタンスを作らずに使うことを目的としたクラスです。

C#
public static class Calculator
{
public static int Add(int a, int b)
{
return a + b;
}
}

そのため、次のコードはコンパイルエラーになります。

C#
// var calculator = new Calculator();

クラス名から直接呼び出します。

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

インスタンスを作りたい場合は、クラス宣言からstaticを外し、必要に応じてインスタンスメソッドへ変更します。

C#
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
}
C#
var calculator = new Calculator();
int result = calculator.Add(10, 20);

8-4. staticフィールドの値が意図せず共有される問題

初心者がつまずきやすいのが、staticフィールドの共有です。

C#
public class ShoppingCart
{
public static List<string> Items { get; } = new();
}
C#
var cart1 = new ShoppingCart();
var cart2 = new ShoppingCart();

ShoppingCart.Items.Add("商品A");
ShoppingCart.Items.Add("商品B");

Console.WriteLine(ShoppingCart.Items.Count); // 2

この設計では、すべての買い物かごが同じ商品リストを共有します。ユーザーごとに買い物かごを分けたい場合には不適切です。

インスタンスプロパティに修正します。

C#
public class ShoppingCart
{
public List<string> Items { get; } = new();
}
C#
var cart1 = new ShoppingCart();
var cart2 = new ShoppingCart();

cart1.Items.Add("商品A");
cart2.Items.Add("商品B");

Console.WriteLine(cart1.Items.Count); // 1
Console.WriteLine(cart2.Items.Count); // 1

8-5. エラー別に見る修正前・修正後のサンプルコード

インスタンスメンバーを直接参照している場合

修正前は次のとおりです。

C#
public class Product
{
public decimal Price { get; set; }

public static void ShowPrice()
{
Console.WriteLine(Price);
}
}

インスタンスを引数として受け取るように修正します。

C#
public class Product
{
public decimal Price { get; set; }

public static void ShowPrice(Product product)
{
Console.WriteLine(product.Price);
}
}

staticメンバーをインスタンスから呼び出そうとしている場合

修正前は次のとおりです。

C#
public class NumberHelper
{
public static bool IsEven(int number)
{
return number % 2 == 0;
}
}

var helper = new NumberHelper();

// 推奨されない、またはコンパイルエラーになる呼び出し
// helper.IsEven(10);

クラス名から呼び出します。

C#
bool result = NumberHelper.IsEven(10);

staticクラスにインスタンスメンバーを定義している場合

修正前は次のとおりです。

C#
public static class TextHelper
{
// staticクラスにはインスタンスメンバーを定義できない
// public string Prefix { get; set; } = "";
}

共有メンバーとして定義するならstaticを付けます。

C#
public static class TextHelper
{
public static string Prefix { get; set; } = "";
}

ただし、Prefixを利用者ごとに変更したいなら、クラス自体を通常クラスへ変更します。

C#
public class TextHelper
{
public string Prefix { get; set; } = "";
}

9. C#のstaticを実務で使うサンプルコード

9-1. Mathクラスのような計算ユーティリティ

C#標準のMathクラスは、staticクラスの代表例です。

C#
double squareRoot = Math.Sqrt(16);
double absolute = Math.Abs(-10);
double rounded = Math.Round(3.14159, 2);

同様に、業務で利用する計算処理をまとめられます。

C#
public static class DiscountCalculator
{
public static decimal CalculateDiscount(
decimal price,
decimal discountRate)
{
ValidateRate(discountRate);
return price * discountRate;
}

public static decimal CalculateDiscountedPrice(
decimal price,
decimal discountRate)
{
return price - CalculateDiscount(price, discountRate);
}

private static void ValidateRate(decimal discountRate)
{
if (discountRate < 0 || discountRate > 1)
{
throw new ArgumentOutOfRangeException(
nameof(discountRate),
"割引率は0以上1以下で指定してください");
}
}
}
C#
decimal result =
DiscountCalculator.CalculateDiscountedPrice(5000m, 0.2m);

Console.WriteLine(result); // 4000.0

9-2. ログ出力用のstaticメソッド

簡単なアプリケーションでは、ログ出力処理をstaticメソッドにまとめることがあります。

C#
public static class AppLogger
{
public static void Info(string message)
{
Write("INFO", message);
}

public static void Error(string message)
{
Write("ERROR", message);
}

private static void Write(string level, string message)
{
Console.WriteLine(
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{level}] {message}");
}
}
C#
AppLogger.Info("処理を開始しました");

try
{
throw new InvalidOperationException("サンプルエラー");
}
catch (Exception ex)
{
AppLogger.Error(ex.Message);
}

ただし、実務ではログの出力先、ログレベル、テスト時の差し替えなどが必要になります。その場合は、ロギング用のインターフェースやフレームワークを利用する設計が適しています。

staticロガーは簡単ですが、大規模なアプリケーションでは柔軟性が不足することがあります。

9-3. 入力チェック用のValidatorクラス

状態を持たない入力チェックは、staticクラスにまとめやすい処理です。

C#
using System.Net.Mail;

public static class Validator
{
public static bool IsRequired(string? value)
{
return !string.IsNullOrWhiteSpace(value);
}

public static bool IsWithinLength(
string? value,
int minimum,
int maximum)
{
if (value is null)
{
return false;
}

return value.Length >= minimum &&
value.Length <= maximum;
}

public static bool IsValidEmail(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return false;
}

try
{
var address = new MailAddress(value);
return address.Address == value;
}
catch (FormatException)
{
return false;
}
}
}
C#
string email = "user@example.com";

if (!Validator.IsRequired(email))
{
Console.WriteLine("メールアドレスを入力してください");
}
else if (!Validator.IsValidEmail(email))
{
Console.WriteLine("メールアドレスの形式が正しくありません");
}
else
{
Console.WriteLine("入力内容は正常です");
}

複雑な業務ルールや、データベースの内容を参照する検証については、通常クラスとして設計した方が扱いやすい場合があります。

9-4. 設定値を管理するstatic readonlyプロパティ

C#では、プロパティへ直接readonlyキーワードを付けてstatic readonlyプロパティを作ることはできません。

次のような構文は使用できません。

C#
// 使用できない
// public static readonly string ApplicationName { get; }

読み取り専用の共有値を公開したい場合は、static readonlyフィールドを使う方法があります。

C#
public static class AppSettings
{
public static readonly string ApplicationName = "SampleApp";

public static readonly TimeSpan RequestTimeout =
TimeSpan.FromSeconds(30);
}

外部にフィールドを直接公開したくない場合は、private static readonlyフィールドと、getterだけのstaticプロパティを組み合わせます。

C#
public static class AppSettings
{
private static readonly string _applicationName = "SampleApp";

private static readonly TimeSpan _requestTimeout =
TimeSpan.FromSeconds(30);

public static string ApplicationName => _applicationName;

public static TimeSpan RequestTimeout => _requestTimeout;
}

単純なgetterのみの自動実装プロパティも利用できます。

C#
public static class AppSettings
{
public static string ApplicationName { get; } = "SampleApp";

public static TimeSpan RequestTimeout { get; } =
TimeSpan.FromSeconds(30);
}

9-5. インスタンスで管理すべき処理との比較例

送料の計算方法が常に同じなら、staticメソッドでも実装できます。

C#
public static class ShippingCalculator
{
public static decimal Calculate(decimal weight)
{
return weight <= 5 ? 500m : 1000m;
}
}

しかし、配送会社によって計算方法が異なる場合、staticメソッドだけでは柔軟性が不足します。

C#
public interface IShippingCalculator
{
decimal Calculate(decimal weight);
}

public class StandardShippingCalculator : IShippingCalculator
{
public decimal Calculate(decimal weight)
{
return weight <= 5 ? 500m : 1000m;
}
}

public class ExpressShippingCalculator : IShippingCalculator
{
public decimal Calculate(decimal weight)
{
return weight <= 5 ? 1000m : 2000m;
}
}

利用する計算方法を実行時に差し替えられます。

C#
public class OrderService
{
private readonly IShippingCalculator _shippingCalculator;

public OrderService(IShippingCalculator shippingCalculator)
{
_shippingCalculator = shippingCalculator;
}

public decimal CalculateShippingFee(decimal weight)
{
return _shippingCalculator.Calculate(weight);
}
}
C#
var standardService =
new OrderService(new StandardShippingCalculator());

var expressService =
new OrderService(new ExpressShippingCalculator());

Console.WriteLine(standardService.CalculateShippingFee(3));
Console.WriteLine(expressService.CalculateShippingFee(3));

単純で変化しない処理にはstatic、複数の実装を切り替える処理にはインターフェースと通常クラス、という使い分けができます。

10. staticに関するよくある質問

10-1. staticはメモリに残り続けるのか?

staticメンバーは特定のインスタンスではなく、型に関連付けて管理されます。一般的には、その型が読み込まれている間、staticフィールドの値は保持されます。

ただし、「必ずアプリケーション終了まで永久に残る」とだけ覚えると正確ではありません。実際の寿命は、アプリケーションの実行環境や型が読み込まれたコンテキストなどの影響を受けます。

通常のアプリケーション開発では、変更可能なstaticフィールドに大きなオブジェクトや大量のデータを保持すると、長期間メモリを占有する可能性があると考えておくとよいでしょう。

C#
public static class Cache
{
public static List<byte[]> Data { get; } = new();
}

このリストへデータを追加し続けると、参照が残るため、不要になったデータが解放されにくくなります。

キャッシュを作る場合は、件数制限、有効期限、削除処理などを設計する必要があります。

10-2. staticを使うと処理速度は速くなるのか?

staticメソッドはインスタンス化せずに呼び出せますが、staticに変更するだけでアプリケーション全体が大幅に高速化するわけではありません。

インスタンス作成が不要になるという違いはありますが、通常は、可読性や設計の適切さを犠牲にしてまで速度だけを理由にstaticを選ぶべきではありません。

パフォーマンスに問題がある場合は、推測でstaticへ変更するのではなく、計測によってボトルネックを特定することが重要です。

staticにするかどうかは、主に次の観点で判断します。

  • インスタンス状態が必要か

  • 値や処理を共有すべきか

  • 実装の差し替えが必要か

  • テストしやすい設計か

10-3. staticとシングルトンの違いは?

staticクラスとシングルトンは、どちらもアプリケーション全体で1つの機能や状態を扱うときに使われるため、混同されやすい概念です。

staticクラスはインスタンスを持ちません。

C#
public static class StaticService
{
public static void Execute()
{
Console.WriteLine("実行しました");
}
}

シングルトンは、インスタンスを1つだけ作成して共有する設計パターンです。

C#
public sealed class SingletonService
{
private static readonly Lazy<SingletonService> _instance =
new(() => new SingletonService());

public static SingletonService Instance => _instance.Value;

private SingletonService()
{
}

public void Execute()
{
Console.WriteLine("実行しました");
}
}
C#
SingletonService.Instance.Execute();

主な違いは次のとおりです。

比較項目staticクラスシングルトン
インスタンス存在しない1つだけ存在する
インターフェース実装できないできる
継承できない設計によって可能
インスタンスメンバー持てない持てる
遅延生成型の初期化に依存Lazy<T>などで制御可能
テスト時の差し替え難しい設計によって可能

ただし、シングルトンも共有状態を持つ設計である点には注意が必要です。どちらを使う場合も、グローバルな変更可能状態を増やしすぎないようにしましょう。

10-4. staticメソッドはオーバーライドできるのか?

staticメソッドはインスタンスに所属しないため、通常の仮想メソッドのようにoverrideできません。

次のような記述はできません。

C#
public class Parent
{
// staticメソッドをvirtualにはできない
public static void Show()
{
Console.WriteLine("Parent");
}
}

public class Child : Parent
{
// overrideはできない
}

派生クラスで同じ名前のstaticメソッドを定義し、newキーワードで基底クラスのメソッドを隠すことはできます。

C#
public class Parent
{
public static void Show()
{
Console.WriteLine("Parent");
}
}

public class Child : Parent
{
public new static void Show()
{
Console.WriteLine("Child");
}
}
C#
Parent.Show(); // Parent
Child.Show(); // Child

これはオーバーライドではなく、メソッドの隠蔽です。実行時のインスタンス型に応じて呼び出し先が変わるポリモーフィズムとは異なります。

実装を差し替えたい場合は、インスタンスメソッドとインターフェースを利用する方が適しています。

C#
public interface IMessageProvider
{
string GetMessage();
}

public class JapaneseMessageProvider : IMessageProvider
{
public string GetMessage()
{
return "こんにちは";
}
}

10-5. staticは初心者でも使ってよいのか?

staticはC#の基本機能なので、初心者でも使って問題ありません。実際、Console.WriteLineMath.Roundなど、日常的に利用する多くのメソッドがstaticです。

C#
Console.WriteLine(Math.Round(3.14159, 2));

ただし、初心者のうちは「インスタンス化が面倒だから」という理由だけで、すべてをstaticにするのは避けましょう。

最初は次の基準で考えると分かりやすくなります。

  • オブジェクトごとに異なる値を使うならインスタンスメンバー

  • 入力値だけを処理する共通機能ならstaticメソッド

  • アプリ全体で共有すべき固定値ならconststatic readonly

  • 変更可能な共有値には慎重になる

  • データベースや外部サービスを使う処理は通常クラスも検討する

staticは便利ですが、利用範囲が広いほど、プログラム全体へ影響を与えやすくなります。役割が明確な小さな共通処理から使い始めるとよいでしょう。

まとめ

C#のstaticは、メソッド、フィールド、プロパティ、クラスなどを、個々のインスタンスではなくクラスそのものに所属させるためのキーワードです。

staticメソッドは、インスタンスを作らずにクラス名から直接呼び出せます。

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

staticフィールドやstaticプロパティの値は、すべてのインスタンスで共有されます。そのため、カウンターやアプリケーション全体の固定値には便利ですが、ユーザーごとの情報やオブジェクトごとに変わる値には適していません。

staticを使うべき代表的な場面は、次のようなケースです。

  • 状態を持たない計算処理

  • 文字列や日付の変換

  • 入力値の検証

  • 拡張メソッド

  • アプリケーション全体で共有する定数

  • インスタンス化する意味がないユーティリティクラス

一方、オブジェクトごとに状態が異なる処理、実装を差し替えたい処理、外部サービスへ依存する処理では、通常クラスやインターフェースを使う設計が適していることがあります。

staticにするか迷ったときは、「この値や処理は、特定のオブジェクトに所属するものか」「アプリケーション全体で共有すべきものか」を考えてください。この違いを意識することで、C#のstaticを適切に使い分けられるようになります。