今回の記事ではクラスのメンバであるフィールドとプロパティをまず解説し、次にアクセス修飾子とスコープについての理解を深めていきます。
ややこしいところですが、頑張っていきましょう!
前回の記事:
クラスにフィールドとプロパティを持たせてみよう
それではフィールドとプロパティについて解説していきます。
この2つのメンバはクラスが持つ変数になります。
フィールド(Field)とは
フィールド(Field)とはクラスが持つ変数になります。
1 2 3 4 5 6 7 8 9 10 |
class A { //フィールドは変数宣言・初期化の様に定義する。 public int Field = 0; public float Field2 = 1f; } //フィールドにアクセスするときは変数の様に扱う var a = new A(); a.Field = 100; a.Field2 = 1234f; |
プロパティ(Property)とは
プロパティ(Property)とはフィールドと同じくクラスが持つ変数のようなものですが、フィールドとは異なり一度メソッドを介してクラスのメンバにアクセスします。
プロパティにはset
メソッドとget
メソッドをそれぞれ定義することができます。両方とも定義する必要はなくどちらか片方のみでもOKです。
- setメソッド:値を設定する時に呼び出される。設定される値はvalueという特殊な変数に保存される。
- getメソッド:値を取得する時に呼び出される。
setメソッドを定義する時、設定される値はvalueという特殊な変数の中に代入されていますので、そちらを使用してください。
基本的に上であげたメソッドを定義する必要がありますが、自動プロパティという省略記法を使うとフィールドのように使うことができます。自動プロパティの時はset
、get
の両方書かないといけません。
<型名> <プロパティ名> { get; set; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
class A { int[] field = new int[2]; public int Prop { get => field[0]; set => field[0] = value; // <- valueの中に設定された値が入っている } public int Prop2 { get => field[1] + 10; set { field[1] = value - 10; } } //どちらか片方のみでもOK public int ReadOnlyProp { get => Prop; } public int WriteOnlyProp2 { set => Prop2 = value; } //自動プロパティ。変数と同じ様に扱える。 public int AutoProp { get; set; } } //使い方はフィールドと同じ。 var a = new A(); a.Prop = 10; var n = a.Prop; // n == 10; a.Prop2 = 0; // a.field[1] == -10 n = a.Prop2; // n == 0; //値の取得・設定は定義されていないとできない。 a.ReadOnlyProp = 10; // <- NG n = a.ReadOnlyProp; // <- OK a.WriteOnlyProp2 = -2; // <- OK n = a.WriteOnlyProp2; // <- NG |
アクセス修飾子とは?
C#にはアクセス修飾子というメンバの使用範囲を指定する機能があります。
プロパティの場合はset
、get
メソッドそれぞれ異なるアクセス修飾子を付けられます。
<アクセス修飾子> <メンバ宣言・定義>
アクセス修飾子には以下のものがあります。
public
:どこからでもアクセスできる。protected
:自身と継承したクラスからアクセスできる。internal
:同じアセンブリ内からしかアクセスできないprotected internal
:自身または同じアセンブリ内か、このクラスを継承したクラスからアクセスできる。private protected
:自身またはこのクラスを継承したクラスからしかアクセスできない。private
:宣言したクラスからしかアクセスできない。何も付けない場合もprivate
になります。
アセンブリという用語が出てきましたが、これはコンパイルによって生成されたDLLファイルや実行ファイルなどを指します。C#ではアセンブリ単位でクラスのメンバのアクセス範囲を区切ることができます。visual studioを使う場合、1プロジェクトが1アセンブリとなるため、internalは同一プロジェクトでアクセス可能となります。
変数やフィールド、プロパティにあるスコープ(Scope)って何?
ここまで意識することはあまりなかったのですが、変数およびクラスのメンバにはそれを宣言・初期化した部分によって使用する範囲が決められています。これにはアクセス修飾子も関係するのですが、それ以外のルールも存在しますのでそれについて解説していきます。
例えば、メソッド内で宣言した変数はそのメソッド内でしか使用することができません。この変数をローカル変数といいます。
この変数を使える範囲のことをプログラミングの世界ではスコープ(Scope)と呼びます。
あるスコープ内では同じ名前のものは必ず一つになるようにする必要があります。型が異なる時でも同じ名前だとコンパイルエラーになるので注意しましょう!
ただし、メソッドやクラス名など文脈上異なるものだとわかる場合は同じ名前でも問題ありません。また、メンバと引数は同じ名前でも問題ありません。その場合は引数が優先して使われます。
変数およびフィールド、プロパティのスコープは次のものがあります。上にあるものほど使用範囲が広くなります。また、これに加えアクセス修飾子による使用範囲の制限も設けられます。
- クラスの静的メンバ(※1)フィールド、プロパティ
- クラスのインスタンスメンバフィールド、プロパティ
- メソッドの引数
- メソッド内で宣言・初期化したローカル変数
- メソッド内で一段階ネスト(※2)した中のローカル変数
- 以降、ネストするたびにスコープは小さくなる。
(※1静的メンバについてはこの次の項目をご覧ください。)
(※2鉤括弧でブロック分けすること。if文やfor文などの鉤括弧も含まれる。)
静的(static)メンバとは
静的メンバはstatic修飾子をつけたメンバのことで、使用する時にインスタンスは不要で代わりにクラス名をつけるメンバになります。静的メンバはプログラムのどこからでも使用することができます。
一般的にスコープが広いほどプログラムに影響する範囲が広くなりますので、極力スコープが小さくなるようにメンバを使っていきましょう!
1 2 3 4 5 6 7 8 9 10 |
class A { public static int Field1; int Field1; // <- NG 静的なものも含めて同じ名前のフィールドは持てない。 static public float Field2; //staticとアクセス修飾子の順番は入れ替えてもいい。 //プロパティやメソッドも静的メンバにできる。 static int Prop {get; set;} static void Method() { } } |
【実践】Unityでスコープの違いを体験してみよう
それでは今回の記事でクラスのフィールドとプロパティについて解説したので実際に使ってみましょう!
と言いたいところですが、これまでの記事のコンポーネントの中で既に使ってきているのでここではスコープについて体験してみましょう!
今回のサンプルはスクリプトの内容がメインになりますので、スクリプトの内容を読んでどのメンバが使用できてどのメンバが使用できないかを見分けられるか試してみましょう!
アクセス修飾子によって使用できるメンバに制限がかかるのを体験してみよう!
次のサンプルコードではアクセス修飾子によってメンバの使用に制限がかかるのを体験できる作りになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
using UnityEngine; public class Sample : MonoBehaviour { [System.Serializable] public class Data { //Dataクラス外からはアクセスできない。 //SerializedFieldを指定するとpublicではないフィールドもInspectorから設定できる。 [SerializeField] float _speed = 0; // publicの時はインスタンスからならどこからでもアクセスできる。 public float Speed { get => _speed; } public Vector3 GetMove() { var move = Vector3.zero; move.x += _speed * Time.deltaTime; // <- OK //move.x += Speed * Time.deltaTime; // <- OK return move; } } //publicなのでこのコンポーネントのインスタンスからならアクセスできる。 public Data data; void Update() { var move = Vector3.zero; move.x += data.Speed * Time.deltaTime; // <- OK //move.x += data._speed * Time.deltaTime; // <- NG transform.position += move; //transform.position += data.GetMove(); // <- OK } } |
SampleコンポーネントのDataクラスには_speedフィールドとそれにアクセスするためのSpeedプロパティがあります。
- _speedフィールド:何もアクセス修飾子がついていない(=private扱い)のでクラス内でしか使用できない。
- Speedプロパティ:publicアクセス修飾子がついているのでクラス外からでも使用できる。
これらのDataクラスのメンバをUpdate()メソッド内で使用していますが、コンパイルエラーになるものにはNGとコメントしています。そのコメントを外すとコンパイル時にエラーが発生するので確認してみてください。
Serialized Field(UnityEngine.SerializeFieldAttribute)について
また、上のサンプルコードではSerializeField(
UnityEngine.SerializeFieldAttribute)
という属性をData._speedフィールドに付けています。
Unityエディタではpublic
なフィールドしかInspectorから設定できないのですが、この属性をつけることでそれ以外のアクセス修飾子がついたフィールドでも設定できるようになります。
プロパティの場合はUnityエディタが対応していないため、Inspectorから設定できません。(エディタ拡張を使用すると無理やり設定することもできますが、基本的にフィールドのみを対象にした方がいいでしょう。)
ブロックによって異なるスコープになるのを体験してみよう!
次のサンプルコードでは2つの色を足し合わせてMeshに設定するものになります。
2つの色を足す際にそれぞれの色をちょっとづつ変えているのですが、その部分で同じ名前のローカル変数(color)を使用しています。このcolor変数は各ブロックで異なる変数として扱われていることに注目してください。
ブロックによるスコープの差を体験するためのものです。あまり実践的ではないサンプルになっていますがお許しください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
using UnityEngine; public class Sample : MonoBehaviour { public Color Color1 = Color.white; public Color Color2 = Color.green; public MeshRenderer Mesh; void Start() { //Color1とColor2の色をそれぞれ少し変更したものを足してMeshに設定する処理 var newColor = Color.black; {//スコープを違うものにしたいのでブロック分けにしている。 var color = Color1; color.r *= 0.5f; newColor += color; } { var color = Color2;//<- 上のブロックのcolorとは異なる color.g = 0.5f; newColor += color; } if(Mesh != null) Mesh.material.color = newColor; } } |
まとめ
ここまでフィールドとプロパティについてみてきました。
今回の記事をまとめると以下になります。
- フィールドとはクラスが持つ変数
- プロパティとはクラスが持つフィールドを便利にしたもの。
- クラスのメンバには使用範囲を表すアクセス修飾子をつけることができる。
- 変数やフィールド、プロパティには現在実行中の処理から使用できるかどうか表すスコープが存在する。
- 同じスコープ内には同じ名前の変数、フィールド、プロパティは一つしか存在できない。
- 処理をブロックで分ける(鉤括弧
{}
で括る)とスコープの管理ができる。 - クラスには静的メンバというアプリのどこからでもアクセスすることができるメンバがある。
- UnityEngine.SerializeFieldAttributeをフィールドに指定するとpublicではなくてもInspectorから値を設定することができる。
それでは次の記事に行ってみましょう!
コメント