静的メンバ

static

クラスにはフィールドとメソッドをつけることができることがわかりました。それらは、インスタンスを生成することにより利用できましたが、インスタンスを生成しなくても利用できるものもあります。それを、静的メンバと呼びます。

静的メンバを定義するためには、先頭に、static(スタティック)修飾子をつけるだけで簡単に定義できます。しかし、呼び出し方や利用方法には様々な制約があります。

サンプルプログラム

は実際に、継承のサンプルを入力して、試してみましょう。以下のプログラムを入力・実行してみてください。

プロジェクトSampleEx201/Data.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleEx201
{
    class Data
    {
        //  Dataオブジェクトの数
        private static int num = 0;
        //  データの値
        private int id;
        //  コンストラクタ(引数つき)
        public Data(int id)
        {
            this.id = id;
            num++;
            Console.WriteLine("値:{0} 数:{1}", this.id, num);
        }
        //  オブジェクトの数を取得
        public static void ShowNumber()
        {
            Console.WriteLine("Dataオブジェクトの数:{0}", num);
        }
    }
}
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleEx201
{
    class Program
    {
        static void Main(string[] args)
        {
            //  Dataクラスを3つ作る
            Data[] d = new Data[2];
            //  Dataのインスタンスの数を表示
            Data.ShowNumber();
            //  一つ目のインスタンスを生成
            for (int i = 0; i < d.Length; i++)
            {
                d[i] = new Data(i*100);
                //  Dataのインスタンスを生成
                Data.ShowNumber();
            }
        }
    }
}
実行結果
Dataオブジェクトの数:0
値:0 数:1
Dataオブジェクトの数:1
値:100 数:2
Dataオブジェクトの数:2

静的メソッド

このプログラムは、Dataクラスのオブジェクトを2つ生成し、消去したものです。しかし、今までやってき多様なものと違うものがいくつかいmられます。まず、Program.csの16行目を見て下さい。ここでは、インスタンスを生成することなく、DataクラスのShowNumber()メソッドを呼び出しています。

ShowNumberメソッドの呼び出し
Data.ShowNumber();

通常、メソッドを呼び出すためには、インスタンスを生成しなくてはなりません。しかし、Data.csの23行目を見てもらえばわかるとおり、メソッドの定義の先頭に、staticがついています。これは、このメソッドが、静的メソッドであることを意味します。静的メソッドとは、インスタンスを生成しなくても利用できるメソッドのことを言います。

静的メソッドを呼び出すときは、(クラス名).(メソッド名)(引数)という形になります。このメソッドは、Dataクラスのメソッドであるため、「Data.」としてから呼び出します。

なお、静的メソッドと区別するために、今までのようにインスタンスを生成しなくてはならないようなメソッドのことを、インスタンスメソッドと呼びます。

静的フィールド

では、続いて、静的メソッドShowNumber()の中を見てみましょう。Data.csの25行目を見てください。ここでは、numというフィールドの値を表示しているだけです。このフィールドの定義(14行目)を見てみましょう。これも、先頭にstaticがついています。メソッドの場合と同様、先頭にstaticがついてフィールドを、静的フィールドと呼びます。

静的なフィールド
private static int num = 0;

メソッドの時と同様、静的フィールドを呼び出すときには、インスタンスを生成していません。通常、静的フィールドを呼び出すときは、(クラス名).(フィールド名)という書式になります。しかし、Data.csの25行目で呼び出している時は、先頭にクラス名がついていません。

同一クラス内からの静的フィールドの呼び出し
Console.WriteLine("Dataオブジェクトの数:{0}", num);

これは、同一クラス内の静的フィールドの呼び出し時には、先頭のクラス名を省略できるからです。これは、インスタンスメソッド内の場合も同様で、Data.csの20行目を見てください。

同一クラス内からの静的メソッドの呼び出し
Console.WriteLine("値:{0} 数:{1}", this.id, num);

このように、インスタンスメソッド内から、静的フィールドを呼び出す場合も、同一クラス内であれば、クラス名が省略できることがわかります。同様のことは、静的なメソッドについても言えます。

なお、メソッドの場合と同様、静的フィールドと区別するために、通常の、インスタンスを生成しなくては利用できないフィールドのことを、インスタンスメソッドと言います。

プログラムの流れ

では、以上を踏まえて、プログラムの流れを見てみましょう。まず、Program.csの16行目を見て下さい。この段階では、Dataクラスのインスタンスは生成されていません。(図2-1.)しかし、staticなフィールドのnumの初期値が0であるので、ここで「 Data.ShowNumber()」メソッドを実行すると、「Dataオブジェクトの数:0」と出力されます。

図2-1:Dataクラスのインスタンスが生成される前の処理
Dataクラスのインスタンスが生成される前の処理

続いて、18行目から23行目のループに入り、Dataクラスのオブジェクトを生成してきます。まず、第一回のループでは、i=0なので、20行目の処理は「data[0] = new Data(0);」という形になります。

このとき、Dataクラスのコンストラクタの処理により、「num++」が実行されるので(Data.cs:19行目)、静的フィールドnumの値が1増えます。そのため、「 Data.ShowNumber()」メソッドを実行すると、「Dataオブジェクトの数:1」と出力されます。(図2-2.)

図2-2:Dataクラスのインスタンスが一つ生成されたときの状態
Dataクラスのインスタンスが一つ生成されたときの状態

続いて、ループの2順目では、「data[1] = new Data(100);」という処理が実行されるため、二つ目のインスタンスが生成され、一つ目の時と同様、Dataのnumフィールドの値がインクリメントされて、numの値が2になります。(図2-3.)

図2-3:Dataクラスのインスタンスの二つ目が生成されたときの状態
Dataクラスのインスタンスの二つ目が生成されたときの状態

以上が、このプログラムの流れです。このサンプルのように、静的なメンバは、オブジェクトの生成とは無関係でありながら、クラスと密接な処理やデータなどを記述するのに適しています。

Mainメソッド

特殊な静的メソッド

ここまで来ると、今まで特にくわしいせつめいもなくつかってきたMainメソッドが、静的なメソッドの一つだということだということがわかると思います。Mainメソッドは、静的メソッドのうち、プログラム実行時に呼び出される処理が記述される特殊なメソッドなのです。

また、Mainメソッドは、一つのプロジェクトに一つしか作れません。つまり、Mainメソッドのあるクラスは、1つのネームスペースに1つしか作れません。

サンプルプログラム

Mainメソッドのあるソースコードの中に、さまざまな処理を盛り込んだプログラムを作ることは可能です。以下のプログラムを実行してみてください。

プロジェクトSampleEx202/Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleEx202
{
    class Program
    {
        //  staticなフィールド
        private static int snum = 100;
        //  インスタンスフィールド
        public int inum = 200;
        //  staticなメソッド
        public static void foo()
        {
            Console.WriteLine("fooメソッド(staticメソッド)");
        }
        public void bar()
        {
            Console.WriteLine("barメソッド(インスタンスメソッド)");
        } 
        static void Main(string[] args)
        {
            //  インスタンスの生成
            Program p = new Program();
            Console.WriteLine("pのインスタンスフィールド:inum = {0}", p.inum);
            Console.WriteLine("Programのクラスフィールド:snum = {0}", snum);
            foo();
            p.bar();
        }
    }
}
実行結果
pのインスタンスフィールド:inum = 200
Programのクラスフィールド:snum = 100
fooメソッド(staticメソッド)
barメソッド(インスタンスメソッド)

自分自身のインスタンスを生成

このクラスは、ProgramクラスのMain()メソッドを呼び出すことより、実行されているわけですが、Program.csの27行目を見てもわかるとおり、この中で、自分自身と同じ、Programクラスのインスタンスを生成しています。このように、クラス内で自分自身のインスタンスを生成することが可能なのです。

これにより、Programクラスのインスタンスpが生成されますが、このインスタンスpは、インスタンスフィールドであるinumやインスタンスメソッドであるbar()にアクセスすることが可能です。

静的メンバの呼び出し

一方、静的フィールドおよび、静的メソッドはというと、Main()の中で、直接呼び出しています。(30、31行目)前述のように、同じクラス内の静的メソッド、およびフィールドであるので、先頭にくつけるクラス名を省略することができます。

ただし、インスタンスフィールドである、inumや、インスタンスメソッドである、bar()を直接呼び出すことはできません。これらはあくまでも、インスタンスを生成してからではなくてはアクセスできないのです。

まとめると、インスタンスメンバは、静的メンバへのアクセスをすることは可能ですが、その逆に、静的メンバからインスタンスメンバにアクセスすることはできません。

練習問題 : 問題2.