C#でプログラミングをしていると、複数のクラス間で同じ変数を共有したいという場面はよくあります。CやPythonといった他のプログラミング言語の場合は、グローバル変数という概念でプログラム全体で利用可能な変数を定義できます。
しかし、C#にはグローバル変数という概念が存在しません。代わりに、staticキーワードやクラス設計を活用して、複数のクラス間で変数を共有する仕組みを利用します。
本記事では、C#でグローバル変数が存在しない理由から、staticを使った代替手段、クラス間での効率的な変数受け渡し方法まで、実践的なコード例とともに詳しく解説します。
目次
閉じる
1.C#にはグローバル変数が存在しない
複数の場所からアクセスできるグローバル変数は便利ですが、C#には原則としてグローバル変数は存在しません。
この章では、まずグローバル変数の概念とC#での扱いについて説明します。
グローバル変数とは
グローバル変数とはプログラムのどこからでも参照できる変数のことです。プログラム全体で共有されるため、変数の値を一箇所で変更すると他の場所での動作に影響を与える可能性があります。
C言語やJavaScriptなど一部のプログラミング言語では、関数の外部で変数を宣言することでプログラム内のどこからでもその変数にアクセスできます。
例えば、C言語では以下のような記述が可能です。
int globalVariable = 100; // グローバル変数
globalVariable = 200; // どこからでもアクセス可能 } |
グローバル変数のメリット・デメリット
グローバル変数には以下のようなメリットとデメリットがあります。
メリット
プログラム全体で共通のデータを簡単に共有できる
変数の受け渡しを考える必要がない
コードの記述量を削減できる場合がある
デメリット
どこから変更されるかわからず、デバッグが困難になる
名前の衝突が発生しやすい
テストが複雑になる
マルチスレッド環境でのデータ競合が発生しやすい
グローバル変数のメリットは、どこからでもアクセスできる利便性です。しかしデメリットとして、プログラムの規模が大きくなるにつれて、予期せぬ場所からの変更によりバグが発生しやすくなることが挙げられます。
C#でグローバル変数を使わない理由
C#では、以下の理由からグローバル変数を採用していません。
オブジェクト指向の原則に反する:C#は完全なオブジェクト指向言語として設計されており、すべてのコードはクラス内に記述する必要があります。グローバル変数は、データの隠蔽やカプセル化といったオブジェクト指向の基本原則に反します。
型安全性の確保:C#は強い型付けを特徴とする言語です。変数のスコープを明確に管理することで、コンパイル時のエラー検出能力を保っています。
保守性の向上:グローバル変数による予期しない副作用を防ぐことで、大規模なアプリケーションでも保守しやすいコードを実現しています。
C#がグローバル変数を提供しない主な理由は、プログラムの安全性を高めるためです。グローバル変数を避けることで、変数のスコープを限定し、コードのモジュール性を高めます。これにより、コードの再利用性やテストの容易性が向上し、大規模な開発プロジェクトでの協調作業がスムーズになります。
ただし、必要に応じて静的メンバーやシングルトンパターンを使用することで、グローバルなデータ共有を実現することは可能です。
2.C#のグローバル変数をstaticで代替する方法
C#でグローバル変数のような機能を実現するには、staticキーワードを使用します。staticメンバはクラスのインスタンスではなく、クラス自体に紐づけられます。
staticキーワードを使用することで、クラスのインスタンスを作成せずに、そのクラスの変数やメソッドにアクセスできるようになります。これにより、グローバル変数のように、プログラム全体で共有されるデータや機能を実装できます。
この章では、具体的な実装方法を詳しく解説します。
staticを使った基本的なグローバル変数の使い方
staticキーワードを使うと、クラスのインスタンスを作成しなくても、その変数にアクセス可能です。
例えば、以下のようにstatic変数とプロパティを定義します。
public class GlobalData { public static int Counter { get; set; } = 0; public static string Message { get; set; } = "初期メッセージ"; }
{ public static void Main(string[] args) { Console.WriteLine(GlobalData.Message); // 初期メッセージ GlobalData.Counter++; Console.WriteLine(GlobalData.Counter); // 1 } } |
上記の例では、GlobalDataクラスのCounterとMessageはstaticであるため、インスタンスを作成せずに直接アクセスできます。GlobalData.Messageでメッセージを参照し、GlobalData.Counter++でカウンターをインクリメントしています。
このように、static変数はプログラムのどこからでも共通のデータにアクセスして変更できる変数です。
static classを使ったグローバル変数の管理方法
static変数を単体で使うよりも、static classを使用してグローバル変数を管理する方法が推奨されることもあります。
static classは、staticメンバのみを持つクラスです。インスタンスを作成できないため、グローバルな変数や関数をまとめるのに適しています。
public static class GlobalSettings { public static string AppName { get; } = "MyApp"; public static int MaxConnections { get; set; } = 100; }
{ public static void Main(string[] args) { Console.WriteLine(GlobalSettings.AppName); // MyApp GlobalSettings.MaxConnections = 200; Console.WriteLine(GlobalSettings.MaxConnections); // 200 } } |
この例では、AppNameとMaxConnectionsはstaticメンバとして定義され、アプリケーション全体で共有されます。AppNameは読み取り専用のプロパティとして定義されているため、実行時に値を変更することはできません。一方、MaxConnectionsは読み書き可能なプロパティとして定義されているため、実行時に値を変更できます。
なお、static classはコンストラクタを持たず、継承もできません。これは、static classがプログラム全体で唯一の存在であることを保証するためです。上記のコード例でいうと、GlobalSettingsクラスはstaticであるため、インスタンス化できません。
このようにstatic classを使うことで、関連するグローバルな変数や関数をまとめて管理して、コードの可読性と保守性の向上につながります。
C#のグローバル変数として配列を使う方法
配列やコレクションもstaticとして定義できます。
配列は、同じ型のデータを複数格納できるデータ構造です。staticな配列を使うことで、プログラム全体で共有される複数の値を一つの変数として管理できます。
public static class GlobalArrays { public static int[] Data { get; set; } = new int[10]; public static string[] Names { get; set; } = { "Alice", "Bob", "Charlie" }; }
{ public static void Main(string[] args) { GlobalArrays.Data[0] = 100; Console.WriteLine(GlobalArrays.Data[0]); // 100 Console.WriteLine(GlobalArrays.Names[1]); // Bob } } |
この例では、GlobalArraysクラス内でstaticな配列DataとNamesを定義しています。Dataはint型の配列で、10個の要素を持つように初期化されています。Namesはstring型の配列で、3個の要素を持ち、それぞれ"Alice"、"Bob"、"Charlie"という文字列で初期化されています。
Mainメソッドでは、GlobalArrays.Data[0]に100を代入し、GlobalArrays.Names[1]の値("Bob")を表示しています。このように、staticな配列を使うことで、複数の値をグローバル変数のように扱えます。
関連記事
C# String Format完全ガイド|0埋め・日付書式・16進数表示・桁数指定の方法を解説
【C#】DateTime完全ガイド:初期化・文字列変換から年月日・時間・ミリ秒操作まで詳細解説
3.C#のクラス間で変数受け渡しする方法
static変数だけでなく、オブジェクト指向の原則に従った変数の受け渡し方法も理解することが重要です。
この章では、適切なクラス間での変数受け渡し方法について説明します。
コンストラクタを使った変数受け渡し
コンストラクタは、クラスのインスタンスを作成する際に実行される特別なメソッドです。コンストラクタに引数を渡すことで、初期値を設定できます。
コンストラクタを使った変数受け渡しは、クラスの初期化時に必要な情報を外部から与えるときに使えます。クラスのインスタンスを作成するときに必要なデータを確実に設定することで、その後の処理で利用できるようになります。
public class DatabaseConnection { private string _connectionString; private int _timeout;
public DatabaseConnection(string connectionString, int timeout = 30) { _connectionString = connectionString; _timeout = timeout; }
public void Connect() { Console.WriteLine($"接続文字列: {_connectionString}, タイムアウト: {_timeout}秒"); } }
{ private DatabaseConnection _connection;
public DataService(DatabaseConnection connection) { _connection = connection; }
public void GetData() { _connection.Connect(); // データ取得処理 } }
class Program { static void Main() { var connection = new DatabaseConnection("Server=localhost;Database=MyDB", 60); var service = new DataService(connection); service.GetData(); } } |
この例では、DatabaseConnectionのインスタンスをDataServiceのコンストラクタに渡し、DataServiceからDatabaseConnectionの接続情報にアクセスしています。DataServiceは、コンストラクタを通じてDatabaseConnectionのインスタンスを受け取り、内部の_connectionStringフィールドや_timeoutフィールドに保持します。これにより、DataServiceはDatabaseConnectionのデータを利用できるようになります。
プロパティを使った変数受け渡し
プロパティは、クラスのデータを外部から安全にアクセスできるようにする仕組みです。プロパティを使用することで、クラスの内部データへのアクセスを制御してデータの整合性を保てます。プロパティにはgetメソッドとsetメソッドがあり、それぞれデータの読み取りと書き込みを制御します。
public class UserSettings { public string Username { get; set; } public string Email { get; set; } public bool NotificationsEnabled { get; set; }
public void DisplaySettings() { Console.WriteLine($"ユーザー名: {Username}"); Console.WriteLine($"メール: {Email}"); Console.WriteLine($"通知: {(NotificationsEnabled ? "有効" : "無効")}"); } }
{ public UserSettings LoadUserSettings(int userId) { // データベースやファイルから設定を読み込む想定 // サンプルでは新規に作成したユーザを返す return new UserSettings { Username = "TestUser", Email = "test@example.com", NotificationsEnabled = true }; } } |
参照渡しとメソッド引数
refキーワードを使うと、変数の参照をメソッドに渡せます。これにより、メソッド内で変数の値を変更すると、呼び出し元の変数にも反映されます。参照渡しは、メソッド内で呼び出し元の変数の値を直接変更する必要がある場合に有効です。
public class Calculator { public void Calculate(ref int result, int value1, int value2) { result = value1 + value2; }
public void ProcessArray(int[] numbers) { // 配列は参照型なので、変更が元の配列に反映される for (int i = 0; i < numbers.Length; i++) { numbers[i] *= 2; } } }
{ public void ExecuteCalculations() { Calculator calc = new Calculator();
// 参照渡しの例 int result = 0; calc.Calculate(ref result, 10, 20); Console.WriteLine($"計算結果: {result}");
// 配列の参照渡しの例 int[] numbers = { 1, 2, 3, 4, 5 }; calc.ProcessArray(numbers);
foreach (int num in numbers) { Console.Write($"{num} "); // 2 4 6 8 10 が出力される } } } |
refキーワードを使用する際には、呼び出し元とメソッドの両方でrefキーワードを指定する必要があることも押さえておきましょう。
なお、outキーワードも参照渡しの一種ですが、refキーワードとは異なりメソッドに渡す前に変数を初期化する必要はありません。outキーワードは、メソッドが複数の値を返す必要がある場合に有効です。outキーワードを使用する際には、メソッド内でoutパラメータに値を必ず代入する必要があります。
public class Program { public static void Divide(int dividend, int divisor, out int quotient, out int remainder) { quotient = dividend / divisor; remainder = dividend % divisor; }
{ int a = 10; int b = 3; int q, r; // 初期化は不要 Divide(a, b, out q, out r); // outキーワードを使用して複数の値を返す Console.WriteLine($"商: {q}, 余り: {r}"); // 商: 3, 余り: 1 } } |
これらの方法を適切に使い分けることで、グローバル変数を使わずに、安全かつ効率的にクラス間でデータを共有できます。
関連記事
C# foreach完全ガイド|基本構文からコレクション操作・LINQ連携・ループ制御まで徹底解説
C# Splitメソッド完全ガイド|複数区切り文字・正規表現・空白削除・最初や最後の要素を取得する方法まで
4.C#グローバル変数の命名規則とベストプラクティス
C#では、グローバル変数の変わりとしてstaticフィールドやstatic classを使用すると紹介しました。これらの変数を使用する場合、適切な命名規則を守ることで、コードの可読性と保守性が大幅に向上します。
この章では、C#におけるstaticフィールドやプロパティの命名規則を紹介します。
C#グローバル変数の命名規則
C#では、staticフィールドとプロパティに対していくつかの命名規則が推奨されています。
パブリックな静的フィールドの場合、PascalCaseといって単語の先頭を大文字にするのが推奨されます。たとえば、GlobalConfiguration、ApplicationSettingsといった表記です。そして、定数はすべて大文字でアンダースコア区切り、または、PascalCase(単語の先頭を大文字にする)で表現します。
また、プライベートな静的フィールドの場合は、camelCaseといって単語の先頭を小文字にします。このとき、アンダースコア(_)を接頭辞としてつけることもあり、Microsoftのドキュメントには、s_を接頭辞としてつけるのが推奨とも記載されています。どの命名規則を使用するかは開発チーム内で統一しておくとよいでしょう。
staticフィールドとプロパティの命名規則の例は以下のとおりです。
public static class GlobalSettings { // パブリック静的フィールド(PascalCase) public static string ApplicationName = "MyApplication";
// プライベート静的フィールド(アンダースコア + camelCase) private static int _connectionCount = 0;
// 定数(PascalCase または UPPER_CASE) public const int MaxConnections = 100; public const string DEFAULT_CONFIG_PATH = "config.json"; } |
可読性を向上させる命名のコツ
C#でstaticフィールドやプロパティを使用するときは、意味のある名前をつけることでコードの意図を明確にできます。
その他、コードの可読性を向上させる命名のコツは以下の通りです。
意味のある名前を使用する:変数の目的や内容を明確に表現する名前を選択します。略語や曖昧な名前は避け、完全な単語を使用します。
一貫性を保つ:プロジェクト全体で統一された命名規則を適用します。チーム内でのコーディングガイドラインに従います。
否定形の名前を避ける:isNotValidよりもisInvalidのような肯定的な表現を使用します。
bool値の変数名:is、has、canなどの接頭辞を使用して、真偽値であることを明確にします。
これらの命名規則に従うことで、コードの可読性が向上して保守しやすくなります。
5.global::演算子の使い方と名前空間管理
C#では複数の名前空間に同じクラス名が存在する場合、global::演算子を使用することでグローバル名前空間に属するクラスを明示的に指定できます。
大規模なプロジェクトでは、異なるライブラリ間で同名のクラスが存在することがあります。global::演算子を使用することで、このような問題を解決できます
この章では、global::演算子の使い方と名前空間の管理方法について紹介します。
global::の基本的な使い方
global::演算子は、名前空間の競合を解決するために導入された機能です。この演算子を使用することで、グローバル名前空間と呼ばれる最上位の名前空間から明示的に型や名前空間を参照できます。
global::演算子の使用例は以下の通りです。
// カスタム名前空間の定義 namespace MyApplication.System { public class Console { public static void WriteLine(string message) { // カスタムConsoleクラスの実装 System.IO.File.AppendAllText("custom_log.txt", message + "\n"); } } }
{ class Program { static void Main() { // 自分で定義したConsoleクラスを使用 System.Console.WriteLine("カスタムConsole出力");
// .NET標準のConsoleクラスを明示的に使用 global::System.Console.WriteLine("標準Console出力");
// 現在時刻の表示でも同様 var now = global::System.DateTime.Now; global::System.Console.WriteLine($"現在時刻: {now}"); } } } |
通常、global::演算子を明示的に使用する必要はありませんが、名前空間の競合が発生した場合に役立ちます。
名前空間競合の解決方法
名前空間の競合は、異なるライブラリやプロジェクトで同じ名前のクラスが定義されている場合に発生します。この場合、global::演算子を使用して、使用したいクラスが属する名前空間を明示的に指定できます。
global::演算子を使用して名前空間の競合を解消する方法は以下の通りです。
using System;
namespace ThirdPartyLibrary { public class MyClass { public static string GetMessage() { return "ThirdPartyLibrary のメッセージ"; } } }
{ using ThirdPartyLibrary;
{ public static string GetMessage() { return "MyApplication のメッセージ"; } }
{ static void Main(string[] args) { // MyApplication の MyClass を使用 Console.WriteLine(MyClass.GetMessage());
Console.WriteLine(global::ThirdPartyLibrary.MyClass.GetMessage()); } } } |
例えば、ThirdPartyLibrary名前空間とMyApplication名前空間の両方にGetMessageクラスが存在する場合、上記のようにglobal::演算子を使用して、どちらのListクラスを使用するかを指定できます。
また、usingエイリアスを使用する方法もあります。
// 独自ライブラリを定義しておく namespace ThirdPartyLibrary { public class List<T> { public void Add(T item) { Console.WriteLine("ThirdPartyLibrary.List.Add() が呼ばれました"); } } }
using StandardConsole = global::System.Console; using StandardList = global::System.Collections.Generic.List<string>; using CustomList = ThirdPartyLibrary.List<string>;
{ public class AliasExample { public void DemonstrateAliases() { // エイリアスを使用して明確にアクセス StandardConsole.WriteLine("標準のConsoleクラスを使用");
StandardList standardItems = new StandardList(); CustomList customItems = new CustomList();
standardItems.Add("標準リスト項目"); customItems.Add("カスタムリスト項目"); } } } |
実際の開発では、global::演算子は多用せずにusingディレクティブとエイリアスを適切に活用するのがおすすめです。また暗黙的global usingディレクティブといって、Systemのようにほぼ確実に使用するライブラリはプロジェクト全体でusingがつくように設定されています。
関連記事
C#とは?基本文法や特徴、メリット、開発分野まで初心者に必要な情報をわかりやすく解説
6.まとめ
C#にはグローバル変数という概念は存在しませんが、staticキーワードやクラス設計を活用することで、グローバル変数のような機能を実現可能です。
C#でグローバル変数が存在しないのは、オブジェクト指向の原則と型安全性を維持するためです。適切な代替手段として、static classの活用、コンストラクタやプロパティを使った変数受け渡し、参照渡しによるデータ共有などの方法があります。これらの手法を適切に使い分けることで、保守性の高いコードを作成できます。
本記事で解説した内容を参考に、C#での変数管理をより効果的に行い、可読性が高く保守しやすいコードを作成できるようにしましょう。
本記事が皆様にとって少しでもお役に立てますと幸いです。
「フリーランスボード」は、数多くのフリーランスエージェントが掲載するITフリーランスエンジニア・ITフリーランス向けの案件・求人を一括検索できるサイトです。
開発環境、職種、単価、稼働形態、稼働日数など様々な条件から、あなたに最適なフリーランス案件・求人を簡単に見つけることができます。
単価アップを目指す方や、自分の得意なスキルを活かせる案件に参画したい方は、ぜひ「フリーランスボード」をご利用ください。
自身に最適なフリーランスエージェントを探したい方はこちらよりご確認いただけます。