静的変数(static variable)・静的メソッド(static method)とは、 特定のインスタンスにではなく、クラスに属するメンバー変数やメソッドのことです。 そのため、静的変数のとこをクラス変数とも呼びます。 (クラス変数という呼び名の方が意味合い的には正しいのですが、 C言語から派生したというC#の歴史的な背景のため、静的変数という呼び方をします。) それに対し、通常のメンバー変数のことをインスタンス変数と呼びます。
クラスのメンバー変数やメソッドを定義する際に、
static
キーワードを付けることで、
その変数は静的メンバー変数・静的メソッドになります。
static 型名 変数名
静的変数・静的メソッドはクラスごとに唯一つの実体を持ち、すべてのインスタンスの間で共有されます。
例として、人間について考えてみましょう。
この場合、特定のインスタンスとは個人個人のこと、
クラスとは人間という種別そのもののことになるわけですが、
名前や年齢などは各個人ごとに異なりますが、
人という種の学名「Homo sapiens」などのように個体によらない共通のものもあります。
したがって、人間をあらわすPersonというクラスを作成した場合、
name(名前)やage(年齢)といったメンバー変数を作りたい場合はインスタンス変数に、scientificName(学名)などのクラス全体で共有すべき変数を作りたい場合は静的変数にすべきです。
(実際には学名などの普遍な値は定数(const)として定義すべきですが、
ここでは説明のためということでご容赦を。
定数の定義については後ほど説明します。)
class Person { public string name; // 名前。個体ごとに違うので、インスタンス変数に。 public int age; // 年齢。同上、インスタンス変数に。 public static string scientificName; // 学名。個体じゃなくて種によって決まるものなので、静的変数に。 }
静的変数はクラスに属する値なので、値を参照するには以下のようにします。
Person p = new Person() p.name = "野上冴子"; // インスタンス変数は [インスタンス名.変数名] で参照する。 p.age = 40; Person.scientificName = "Homo sapiens"; // 静的変数は [クラス名.変数名] で参照する。
また、メソッドに対して static を付けると、 クラスに属するメソッドになります。 (静的変数にしかアクセスできなくなる。 メソッドからインスタンス変数にアクセスする必要が特にない場合には、静的メソッドにしておく方が実行効率がいい。)
静的変数・静的メソッドは、数学関数や数学定数などのような、インスタンスを持つ必要のない関数を定義する場合にも使います。 ( 「関数」 で説明した内容は、 実際には(C# 用語的には)関数ではなく、静的メソッドです。)
using System; class Math { // sin x を求める関数。 static double Sin(double x) { double xx = -x * x; double fact = 1; double sin = x; for(int i=0; i<100; ++i) { fact *= i; ++i; fact *= i; ++i; x *= xx; sin += x / fact; } return sin; } } class StaticSample { static void Main() { Console.Write(Math.Sin(1)); } }
標準ライブラリの Math.Sin や Console.Write などは静的メソッドです。
静的変数の初期化には、通常のコンストラクタではなく、静的コンストラクタ(static constructor)というものを使います。
静的コンストラクタの定義の仕方は、コンストラクタの前に static キーワードを付ける以外は通常のコンストラクタの定義の仕方と同じです。
例えば、先ほどの Person クラスを例に挙げると以下のようになります。
class Person { string name; // 名前。インスタンス変数。 int age; // 年齢。インスタンス変数。 static string scientificName; // 学名。静的変数。 // 通常のコンストラクタ public Person(string name, int age) { this.name = name; this.age = age; } // 静的コンストラクタ static Person() { Person.scientificName = "Homo sapiens"; } }
通常のコンストラクタが新しいインスタンスが生成されるたびに呼び出されるのに対して、 静的コンストラクタはプログラムの初めに1度だけ呼び出されます。
using System; // 1台ごとに固有のIDが振られるような何らかの製品。 class Product { static int id_generator; int id; static Product() { // 最初に1度だけ呼ばれ、id_generator を 0 に初期化。 id_generator = 0; } public Product() { // 新しい製品が製造されるたびに新しい id を振る。 id = id_generator; id_generator++; } /// <summary> /// その製品のIDを取得する。 /// </summary> public int ID { get{return id;} } } class StaticSample { static void Main() { for(int i=0; i<10; i++) { Product p = new Product(); Console.Write("ID: {0}\n", p.ID); } } }
ID: 0 ID: 1 ID: 2 ID: 3 ID: 4 ID: 5 ID: 6 ID: 7 ID: 8 ID: 9
Ver. 2.0
標準ライブラリ中の Math クラスのように、
静的なメンバーしか持たないクラスがあります。
Math クラスに限らず、
static メンバーのみを持ち、インスタンスの作成が不可能なクラスを作りたいことがしばしばあります。
C# 1.0 では、private なコンストラクタを持つ sealed クラスとしてこのようなクラスを作成していました。 このような方法で、「インスタンスが作成不可能」という制約は満たすことが出来ますが、 非 static なメンバーを定義することができてしまうという問題がありました。 (決してアクセスすることの出来ない無駄なメンバーになってしまいます。)
それに対して、C# 2.0 では、
クラス定義時に static をつけることで、
静的メンバーしか定義できないクラスを作ることが出来ます。
このようなクラスを静的クラス(static class)と呼びます。
static class Math { // double x; というような、非 static な変数・メソッドは定義できない。 // sin x を求める関数。 static double Sin(double x) { double xx = -x * x; double fact = 1; double sin = x; for(int i=0; i<100; ++i) { fact *= i; ++i; fact *= i; ++i; x *= xx; sin += x / fact; } return sin; } }
Ver. 3.0
C# 3.0 では、(本来、前置き記法である)静的メソッドを、 インスタンスメソッドと同様に後置き記法で書くことのできる、 拡張メソッドという機能が追加されました。
すなわち、 今までなら、
int x = int.Parse("1"); // "1" よりも Parse が前
と書いていたものを、
static class Extensions { public static int Parse(this string str) { return int.Parse(str); } }
というような静的メソッドを用意することで、 以下のような構文で呼び出せるようになります。
int x = "1".Parse(); // Parse が後に
詳細は 「拡張メソッド」 で説明します。
クラスの問題 1の Point 構造体に、
2点間の距離を求める static メソッド GetDistance を追加せよ。
/// <summary> /// A-B 間の距離を求める。 /// </summary> /// <param name="a">点A</param> /// <param name="b">点B</param> /// <returns>距離AB</returns> public static double GetDistance(Point a, Point b)
また、GetDistance を用いて、
Triangle クラスに三角形の周を求めるメソッド
GetPerimeter を追加せよ。
/// <summary> /// 三角形の周の長さを求める。 /// </summary> /// <returns>周</returns> public double GetPerimeter()