Column - スコープと変数 -
プログラムの中に同じ名前の変数名がいくつも登場することは、決して珍しくない。まったく独立して混乱をきたさない文脈で、同じ名前の変数名を別個に宣言することは、コンパイラを混乱させることはなく、曖昧さもなく、実行コードを生成することができる。しかし、ときとして、コンパイラは混乱しなくてもソースを読み書きする人間が混乱する場合もある。そこで、C#では同じ名前の変数を制限する機能を持っている。以下はその機能を調べるために作成してみたソース・コードである。コンパイル・エラーを見るためのソースなので、実行することはできない。
1: using System;
2:
3: namespace Sample011
4: {
5: class Class1
6: {
7: private static int x=0;
8: static void sample1()
9: {
10: x = 1;
11: int x=2; // ローカルの変数 'x' をこのスコープで宣言することはできません。これは、'親またはカレント' スコープで別の意味を持つ 'x' の意味が変更されるのを避けるためです。
12: }
13: static void sample2()
14: {
15: int x=1;
16: x = 2;
17: }
18: [STAThread]
19: static void Main(string[] args)
20: {
21: if( args[0] == "a" )
22: {
23: int x=1;
24: if( x == 1 )
25: {
26: int x=2; // ローカルの変数 'x' をこのスコープで宣言することはできません。これは、'親またはカレント' スコープで別の意味を持つ 'x' の意味が変更されるのを避けるためです。
27: }
28: }
29: else if( x == 1 ) // 'x' は宣言 'Sample011.Class1.x' と競合しています。
30: {
31: int x=3;
32: }
33: }
34: }
35: }
このソースには、クラスClass1にxという名前のメンバ変数が存在する。そして、それぞれのメソッド内部で、あらためてxというローカル変数を宣言することを試みている。まず8〜12行目のメソッドsample1と、13〜17行目のメソッドsample2を比較していただきたい。sample1は、まずクラスのメンバ変数xにアクセスした後で、ローカル変数xを宣言しようとしている。1つのブロックの中で、同じxという名前が別の変数を示すために使用することは許されておらず、コンパイル・エラーが発生する。しかし、sample2のほうは、15行目も16行目も同じ1つのxを扱っているため、これはエラーにならない。26行目は、22〜28行目の親スコープ内でxという変数が宣言されている状態で、25〜27行目の子スコープで同じ変数名を宣言しようとしたものである。この場合、子スコープ内で異なる変数を同じ内容で参照しようとしたわけではないがエラーになる。29行目は、クラスのメンバ変数xを参照しようとしているが、このメソッド内ではすでにxというローカル変数が宣言されておりエラーになる。
このエラーに遭遇した場合、変数名を変更することが必要となる。変数名を、区別できる分かりやすい名前に書き換えることは、ソースを分かりやすくすることにも貢献するので、ぜひ行うべきだろう。
Column - constとreadonly -
変数を宣言する際に、それが決まった値を持ち、変更されないことを指定する機能がある。1つはconstキーワードを付加する方法であり、もう1つはreadonlyキーワードを付加する方法である。より正確にいえば、constキーワードは変数ではなく定数であることを示すもので、readonlyキーワードは読み取り専用で代入できない変数を示すものである。この2つは同じように見えるかもしれないが、機能は同等ではない。以下はそれを示したサンプル・ソースである。
1: using System;
2:
3: namespace Sample012
4: {
5: class Class1
6: {
7: private const int c=123;
8: private readonly int r;
9: public Class1( int n )
10: {
11: r = n;
12: }
13: public void dump()
14: {
15: Console.WriteLine("{0},{1}",c,r);
16: // r = 321; // 読み取り専用フィールドに割り当てることはできません (コンストラクタ、変数初期化子では可 )。
17: }
18: [STAThread]
19: static void Main(string[] args)
20: {
21: Class1 instance1 = new Class1(456);
22: instance1.dump();
23: Class1 instance2 = new Class1(789);
24: instance2.dump();
25: }
26: }
27: }
これを実行すると以下のようになる。
まず7行目では、constを用いて定数を宣言している。8行目では、readonlyを用いて読み出し専用の変数を宣言している。最初に注目していただきたいのは、初期化するタイミングである。constは、宣言の中で値を指定する。readonlyでも同じことはできるが、それだけではなく、11行目のようにコンストラクタの中で値を代入して初期化することもできる。しかし、通常のメソッド内から書き込もうとすると16行目のようにコンパイル・エラーになる。この違いの結果、constは何があっても必ず同じ値を持つのに対して、readonlyは実行時に当てはめる値を決定することが可能になる。
両者の相違は、複数のインスタンスを作成する場合にも出てくる。constは、どのインスタンスから参照しても同じ値になるが、readonlyは必ずしも同じ値になるとは限らない。つまり、readonlyは(staticとして宣言されていないメンバ変数であれば)インスタンスの中にある変数の1つであると見なすことができる。一方、constはインスタンスの一部ではなく、独立した定義と見なすと分かりやすい。実際に、readonlyをstaticにするにはstaticキーワードを付ける必要があるが、constは最初からstaticであるかのように扱うことができる。
『新プログラミング環境 C#がわかる+使える』
本記事は、(株)技術評論社が発行する書籍『新プログラミング環境 C#がわかる+使える』から許可を得て一部分を転載したものです。
【本連載と書籍の関係について 】
この書籍は、本フォーラムで連載した「C#入門」を大幅に加筆修正し、発行されたものです。連載時はベータ版のVS.NETをベースとしていましたが、書籍ではVS.NET製品版を使ってプログラムの検証などが実施されています。技術評論社、および著者である川俣晶氏のご好意により、書籍の内容を本フォーラムの連載記事として掲載させていただけることになりました。
→技術評論社の解説ページ
ご注文はこちらから
Copyright© Digital Advantage Corp. All Rights Reserved.