constとreadonly
変数を宣言する際に、それが決まった値を持ち、変更されないことを明示的に指定する機能がある。1つは、constキーワードを付加する方法であり、もう1つはreadonlyキーワードを付加する方法である。より正確にいえば、constキーワードは変数ではなく定数であることを示すもので、readonlyキーワードは読み取り専用で代入できない変数であることを示すものである。この2つは同じように見えるかもしれないが、機能は同等ではない。以下はそれを示したサンプルソースである。
1: using System;
2:
3: namespace ConsoleApplication8
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: static void Main(string[] args)
19: {
20: Class1 instance1 = new Class1(456);
21: instance1.dump();
22: Class1 instance2 = new Class1(789);
23: instance2.dump();
24: }
25: }
26: } |
|
constとreadonlyの違いを示すサンプル・プログラム8 |
constは宣言の中でのみ値を設定できるが、readonlyではコンストラクタの中でも値を設定できる。 |
これを実行すると以下のようになる。
|
サンプル・プログラム8の実行結果 |
複数のインスタンスを作成する場合、constはどのインスタンスでも同じ値となるが、readonlyでは必ずしもそうなるとは限らない。
|
まず7行目ではconstを用いて定数を宣言し、8行目ではreadonlyを用いて読み出し専用の変数を宣言している。最初に注目していただきたいのは、初期化するタイミングである。constは、宣言の中で値を指定する。readonlyでも同じことはできるが、それだけではなく、11行目のようにコンストラクタの中で値を代入して初期化することもできる。しかし、通常のメソッド内から書き込もうとすると、16行目のようにコンパイル・エラーになる。この違いの結果、constは何があっても必ず同じ値を持つのに対して、readonlyは実行時に当てはめる値を決定することが可能になる。
両者の相違は、複数のインスタンスを作成する場合にも現れる。constは、どのインスタンスから参照しても同じ値になるが、readonlyは必ずしも同じ値になるとは限らない。つまり、readonlyは(staticとして宣言されていないメンバ変数であれば)インスタンスの中にある変数の1つであると見なすことができる。しかし、constはインスタンスの一部ではなく、独立した定義と見なすと分かりやすい。実際に、readonlyをstaticにするにはstaticキーワードを付ける必要があるが、constは最初からstaticであるかのように扱うことができる。
スレッド切り替えをロックするlockステートメント
この連載では主に言語仕様を扱うため、主にクラス・ライブラリ経由で機能が提供されるスレッドについては扱ってこなかった。しかし、言語仕様の中にもスレッドと関係するものがある。それはlockステートメントである。これは、ある参照型の値へのアクセスを特定のスレッドで独占するために使用することができる。
例えば、以下のようなソースがあったとする。HelloWorldとC#という文字列を出力するスレッドが2本並行して動作する。
1: using System;
2: using System.Threading;
3:
4: namespace ConsoleApplication9
5: {
6: class Class1
7: {
8: private void sample1()
9: {
10: for( int i=0; i<1000; i++ )
11: {
12: Console.Write("Hello");
13: Console.Write("World ");
14: }
15: }
16: private void sample2()
17: {
18: for( int i=0; i<1000; i++ )
19: {
20: Console.Write("C");
21: Console.Write("# ");
22: }
23: }
24: static void Main(string[] args)
25: {
26: Class1 instance = new Class1();
27: Thread thread1 = new Thread( new ThreadStart(instance.sample1) );
28: Thread thread2 = new Thread( new ThreadStart(instance.sample2) );
29: thread1.Start();
30: thread2.Start();
31: }
32: }
33: } |
|
2つのスレッドが同時に実行されるサンプル・プログラム9 |
スレッドとはOSが実行時間を割り当てる基本単位であり、1つのプログラムから複数のスレッドを実行することができる。 |
これを実行すると以下のようになる。
|
サンプル・プログラム9の実行結果 |
文字列の出力途中でスレッドの切り替えが行われ、「HelloC#……」となってしまった部分がある。
|
HelloとWorld、Cと#はそれぞれ独立した出力呼び出しで出力されており、途中でスレッド切り替えが入ると容易にHelloとWorld、Cと#は泣き別れになってしまう。実際に実行結果画面の中に、Helloの次がWorldにならずCが入り込んでいる例があるのが分かると思う。このような割り込みを抑止して、必ずHelloとWorldは連続して出力するようにするには以下のようにソースを修正する。
1: using System;
2: using System.Threading;
3:
4: namespace ConsoleApplication10
5: {
6: class Class1
7: {
8: private void sample1()
9: {
10: for( int i=0; i<1000; i++ )
11: {
12: lock( this )
13: {
14: Console.Write("Hello");
15: Console.Write("World ");
16: }
17: }
18: }
19: private void sample2()
20: {
21: for( int i=0; i<1000; i++ )
22: {
23: lock( this )
24: {
25: Console.Write("C");
26: Console.Write("# ");
27: }
28: }
29: }
30: static void Main(string[] args)
31: {
32: Class1 instance = new Class1();
33: Thread thread1 = new Thread( new ThreadStart(instance.sample1) );
34: Thread thread2 = new Thread( new ThreadStart(instance.sample2) );
35: thread1.Start();
36: thread2.Start();
37: }
38: }
39: } |
|
lockステートメントを使用したサンプル・プログラム10 |
lockステートメントによりほかのスレッドが割り込んでこないように、スレッド切り替えをロックすることができる。 |
これを実行すると以下のようになる。
|
サンプル・プログラム10の実行結果 |
2つの文字列が交じって出力されるが、“HelloWorld”と“C#”自体は分断されずに正しく出力されている。
|
12〜16行目、23〜27行目のlockステートメントが、その内部の実行中にもう1つのスレッドが割り込んでこないようにスレッド切り替えをロックしてしまっている。しかし、すべてのスレッド切り替えをロックしているわけではなく、lockステートメントで指定された参照(ここではthis、つまり自分自身のインスタンス)をロックしようとするスレッドへの切り替えを抑止するのである。この結果、実行結果画面のように文字列の泣き別れがなくなったことが分かると思う。
Insider.NET 記事ランキング
本日
月間