今回はC#のクラス、フィールド、メソッド、コンストラクタ、プロパティについてみていきます。
C#でクラスを作成してみます。
C#はオブジェクト指向言語なのでクラスを作成しそこにフィールドとメソッドを定義してオブジェクトを作成します。
フィールドはそのオブジェクトが保持しておく値、メソッドがそのオブジェクトが出来る操作ですね。
スクリプトの作成はUnity付属のMonodevelopを使用して作成していきます。
作成したクラスはMainClassというUnityのMonoBehaviourクラスを継承したクラスで実行する形を取っていきます。
UnityでC#のスクリプトをLearn1_1というファイル名で作成すると
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using UnityEngine; using System.Collections; public class Learn1_1 : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } } |
というスクリプトがデフォルトで作成されます。
Unityの場合はMonoBehaviourクラスを継承してLearn1_1クラスが作成されるので、MonoBehaviourクラスで定義されているStartメソッドやUpdateメソッドを
子クラスであるLearn1_1でも使えるんですね。
継承はまた別の記事でやりますのでそれまで置いておきます。
C#のクラスの作成の仕方
通常のC#クラスを作るとすれば
1 2 3 4 5 6 7 8 9 | // Objectクラスを継承して作成 public class Learn1_1 : object { } // 何も継承せず作成 public class Learn1_1 { } |
と作成し外部のスクリプトから使用します。
objectクラスを継承した形でクラスを定義してもobjectクラスは自動で継承されます。
名前空間にクラスを入れる
C#では名前空間と呼ばれるものがあります。
この名前空間はJavaで言えばパッケージと同じようなものでその名前空間にクラスを入れて同一クラスでも名前空間で分けてクラス定義する場合は便利です。
あえて同じ名前のクラスを作成し名前空間で分けて使うというよりも複数人で開発したクラスを統合する時にクラス名の競合を回避する為に使うという感じですね。
この名前空間はさきほどのスクリプトでも出ていて、
1 2 3 | using UnityEngine; |
の部分でUnityEngine名前空間に定義されているクラスをUnityEngineの部分を省略して使用する事が出来るようにしています。
その為usingディレクティブを使わなければ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class Learn1_1 : UnityEngine.MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } } |
↑のように記述出来ますね。
では、Learn1_1をLearn1という名前空間に定義しそれを使ってみます。
1 2 3 4 5 6 7 8 | namespace Learn1 { public class Learn1_1 { } } |
namespaceに続けてLearn1を記述し、その中にクラスを内包する形で定義します。
このLearn1_1クラスを外部クラスから使ってみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | using UnityEngine; using System.Collections; using System; using Learn1; public class TestClass : MonoBehaviour { // Use this for initialization void Start () { Learn1_1 learn = new Learn1_1 (); Debug.Log (learn); } } |
Learn1_1クラスをインスタンス化(インスタンス化については後述します)していますが、Learn1_1クラスはLearn1名前空間で定義されているので、
using Learn1;
と記述しLearn1.Learn1_1と記述しなくてもいいようにしています。
アクセス修飾子
さきほどのクラス定義の部分に出てきたpublicの部分がアクセス修飾子ですね。
publicは他のアセンブリ、他のクラスからもアクセス出来ます。
privateは自身のクラスからしかアクセス出来ません。
protectedは自身か自身を継承した子クラスからしかアクセス出来ません。
他にinternal、internal protected(継承関係のクラス)という別のアセンブリからはアクセス出来ないというのもあるみたいですね。
メソッド定義
クラスには操作を担当するメソッドがあります。
Learn1_2クラスにTalkとRunメソッドを記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using UnityEngine; using System.Collections; public class Learn1_2 { // 静的メソッド public static void Talk() { Debug.Log ("喋ります"); } public void Run() { Debug.Log ("走ります"); } } |
メソッドにも同じようにアクセス修飾子を付け外部アセンブリ、クラスからのアクセスの制限を加えています。
↑のようにTalkにはstatic修飾子を付けています。
これは静的という意味でクラスをインスタンス化せずに実行出来るようになります。
インスタンスはオブジェクトから実体を作成したもので、オブジェクトはたい焼きの鉄板、インスタンスはそれから作成した個々のたい焼きになります。
戻り値
voidの部分は戻り値の型を指定していて、何らかの処理をして値や参照をメソッドを呼び出したところに返す必要がある時にその値の型を指定します。
voidの場合は戻り値がない事を表しています。
例えば計算したint型の値を呼び出し元に返す場合は
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using UnityEngine; using System.Collections; public class CalculateScript { void Start () { int intValue = Calculate(); } // 静的メソッド public static int Calculate() { int returnValue = 1 + 1; return returnValue; } } |
とメソッドに戻り値の型を指定し、returnでint型の値を返します。
呼び出し元のStartメソッドではCalculateの呼び出しで2が返り、それがintValueに代入されます。
戻り値の型にvoidが指定されている時でもreturnは使う事が出来て。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using UnityEngine; using System.Collections; public class VoidTestScript { void Start () { VoidTest(); } // 静的メソッド public static void VoidTest() { Debug.Log("voidtest"); return; } } |
上のようにreturnに戻り値を指定しないようにします。
returnを使うと呼び出し元に戻りますので、ある条件までfor文等で繰り返し目的の処理が完成した時などにreturnを使って即座に呼び出し元に戻したい時等に使用出来ます。
ただreturnを使うとその後のメソッド内の処理が行われず呼び出し元に戻るので、使う際には注意する必要があります。
それでは実際にLearn1_2クラスのTalkとRunメソッドを呼び出してみましょう。
Learn1_2クラスのメソッドを呼び出す新しいMainClassクラスを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class MainClass : MonoBehaviour { // Use this for initialization void Start () { // 静的メソッドはインスタンス化しなくても実行出来る Learn1_2.Talk (); // Learn1_2をインスタンス化 Learn1_2 learn = new Learn1_2 (); // Learn1_2に定義されているRunメソッドを呼び出す learn.Run (); } } |
Talkは静的メソッドなのでクラスをインスタンス化せずにクラス名の後にTalk()を記述して呼び出せます。
クラスをインスタンス化(実体化)するには
クラス名 変数名 = new クラス名();
と記述します。
インスタンス化したクラスを参照するのがlearn変数なのでlearnを通してRunメソッドを呼び出します。
Learn1_2クラスにstatic修飾子を取りつけた場合
1 2 3 4 | public static class Learn1_2 { } |
↑のようになりますが、静的なクラスはインスタンス化する事が出来なくなります。
また静的なクラスの場合はメソッド等は全てstaticがついた静的なメンバーである必要があります。
フィールド定義
メソッドに続きフィールドも定義してみます。
Learn1_3クラスを作成します。
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 | using UnityEngine; using System.Collections; public class Learn1_3 { private string talk; private int hp; private float speed; public void SetTalk(string talk) { this.talk = talk; } public string GetTalk() { return talk; } public void SetHp(int hp) { this.hp = hp; } public int GetHp() { return hp; } public void SetSpeed(float speed) { this.speed = speed; } public float GetSpeed() { return speed; } } |
↑のようにフィールドをprivate修飾子を付けて型と変数名を付けて宣言してます。
フィールドはクラス全体で参照出来る変数といった感じです。
アクセス修飾子にprivateを設定しているのでこのクラスからしかアクセス出来ないので外部のクラスからフィールドの値を設定したり、フィールドの値を参照したりが出来るようにSetterとGetterを用意しています。
1 2 3 4 5 | public void SetTalk(string talk) { this.talk = talk; } |
SetTalkを見ると引数として文字列型(string)のtalk変数に呼び出し元から渡された値が入ります。
値の渡し方は値型と参照型で異なりますが今回はスルーしていきます。
引数の変数名をtalkとしたことで、フィールド名のtalkと名前がかぶってしまいました。
そこでthis.talkと記述することでこのクラスのフィールドであるtalkを指定しています。
もしここを
talk = talk;
としてしまうと引数として受け取ったtalkを引数のtalkに入れるだけでフィールドの操作はまったく行われません。
それではLearn1_3クラスをインスタンス化しフィールドの操作をしてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | using UnityEngine; using System.Collections; public class MainClass : MonoBehaviour { // Use this for initialization void Start () { Learn1_3 learn = new Learn1_3 (); learn.SetTalk ("テスト"); learn.SetHp (10); learn.SetSpeed (5.5f); Debug.Log (learn.GetTalk ()); Debug.Log (learn.GetHp ()); Debug.Log (learn.GetSpeed ()); } } |
Learn1_3クラスをインスタンス化しそれぞれのフィールドにSetterで値を設定した後、Getterで値を取得しUnityのコンソールに表示しています。
上記のスクリプトを実行すると
テスト
10
5.5
と表示されます。
SetterとGetterをフィールド毎に設定していますが、この面倒臭い記述を無くしてプロパティを使ってアクセスする事も出来ます。
それは後でやります。
フィールドの初期化を行うコンストラクタ
クラスをインスタンス化した時にフィールドの初期化等をしたい事があります。
そんな時に使用するのがコンストラクタです。
コンストラクタはクラス名にアクセス修飾子を付けただけのものを書きます。
コンストラクタを定義していなければデフォルトコンストラクタが呼ばれます。
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 37 38 39 40 41 42 43 | using UnityEngine; using System.Collections; public class Learn1_4 { private string talk; private int hp; private float speed; // コンストラクタ public Learn1_4() : this("テスト", 10, 5.5f) { } public Learn1_4(string talk) : this(talk, 10, 5.5f) { } public Learn1_4(string talk, int hp) : this(talk, hp, 5.5f) { } public Learn1_4(string talk, int hp, float speed) { this.talk = talk; this.hp = hp; this.speed = speed; } // 以降ゲッター public string GetTalk() { return talk; } public int GetHp() { return hp; } public float GetSpeed() { return speed; } } |
コンストラクタが4つ宣言されています。
コンストラクタやメソッドは引数の型や数の違いによって同名でも引数に応じたものを呼び出すオーバーロードという機能が利用出来ます。
フィールドの値を初期化したいけど全部の引数を指定しなくてもいい場合は1つの引数だけを指定して呼び出したりします。
4つのコンストラクタそれぞれでフィールドの初期化処理のコードを書くのは大変なので: thisを使って自身の他のコンストラクタを呼び出しています。
例えば引数に何も設定しないでインスタンス化した場合は
1 2 3 4 | public Learn1_4() : this("テスト", 10, 5.5f) { } |
のコンストラクタが実行されますが、: thisを使って同じコンストラクタの引数が3つのものを呼び出しています。
こうすると引数に何も設定されていないでインスタンス化されたオブジェクトのフィールドに既定値を設定する事が出来ます。
実行順序はLearn1_4()の中身を実行した後に引数付きのコンストラクタを実行します。
フィールドの初期化をもっと簡単に
: thisを使うと引数によって呼び出すコンストラクタを変更すればいいことはわかりましたが、これでも同じような記述が増えますね。
それをもっと簡単にすると
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 | using UnityEngine; using System.Collections; public class Learn1_5 { private string talk; private int hp; private float speed; // コンストラクタ public Learn1_5(string talk = "テスト", int hp = 10, float speed = 5.5f) { this.talk = talk; this.hp = hp; this.speed = speed; } // 以降ゲッター public string GetTalk() { return talk; } public int GetHp() { return hp; } public float GetSpeed() { return speed; } } |
コンストラクタを引数の個数毎に作成せず、あらかじめ空であれば設定する値を入れておくだけで大丈夫なようです。
このクラスをインスタンス化し実行してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | using UnityEngine; using System.Collections; public class MainClass : MonoBehaviour { // Use this for initialization void Start () { Learn1_5 learn = new Learn1_5 ("テスト"); Debug.Log (learn.GetTalk ()); Debug.Log (learn.GetHp ()); Debug.Log (learn.GetSpeed ()); } } |
Startメソッド内でLearn1_5クラスをインスタンス化しゲッターを呼んでフィールドの値を表示しています。
実行すると
テスト
10
5.5
がUnityのコンソールに表示されます。
フィールドのSetterとGetterを簡単にする
フィールドに値を設定したり取得したりといった事はSetterとGetterメソッドを作成しそれぞれにアクセスしていました。
ですがフィールドが多くなってくるとそれぞれにアクセスするメソッドを定義するだけで大変ですし、プログラムのコード量が増えてしまいますね。
そこでフィールドをプロパティを自動実装する記述に変更しSetterとGetterの記述をなくします。
プロパティ名の最初は大文字にするみたいです。
Learn1_5をプロパティの記述に変えてLearn1_6クラスとします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | using UnityEngine; using System.Collections; public class Learn1_6 { // プロパティ public string Talk { get; private set; } public int Hp { get; private set; } public float Speed { get; private set; } // コンストラクタ public Learn1_6(string talk = "テスト", int hp = 10, float speed = 5.5f) { this.Talk = talk; this.Hp = hp; this.Speed = speed; } } |
↑のようにアクセス修飾子をpublicにしプロパティ名を記述し、get、setを書きます。
プロパティに値を設定するのはコンストラクタのみで指定する場合はprivate修飾子を付け外部スクリプトから操作出来ないようにします。
実際にLearn1_6クラスをインスタンス化して使ってみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | using UnityEngine; using System.Collections; public class MainClass : MonoBehaviour { // Use this for initialization void Start () { Learn1_6 learn = new Learn1_6 ("テスト"); Debug.Log (learn.Talk); Debug.Log (learn.Hp); Debug.Log (learn.Speed); // 実行するとエラー発生 //learn.Talk = "書き換え文字列"; } } |
Learn1_6クラスを作成し、プロパティの値をコンソールに表示してます。
learn.Talk = “書き換え文字列”;
ではプロパティをprivateに設定している為、エラーになります。
値を書き換える為にはプロパティのアクセス修飾子privateを消します。
実行すると
テスト
10
5.5
と表示されます。
プロパティをフィールドのアクセサーとして使う
次にフィールドの操作をプロパティから行うようにしてみます。
まずはLearn1_8クラスを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using UnityEngine; using System.Collections; public class Learn1_8 { private string talk; private int hp; private float speed; // プロパティ public string Talk { get { return talk; } set { talk = value; } } public int Hp { get { return hp; } set { hp = value; } } public float Speed { get { return speed; } set { speed = value; } } } |
フィールドをprivateにし、プロパティをpublicにしてget、setのアクセス制限のprivateを外し、プロパティを介してフィールド値を操作出来るようにします。
プロパティを介する事で値のチェック等もする事が出来ます。
プロパティのsetのprivateを外した事で、Learn1_8クラスをインスタンス化し使用する時にオブジェクト初期化子を使って、プロパティ値を設定する事が出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | using UnityEngine; using System.Collections; public class MainClass : MonoBehaviour { // Use this for initialization void Start () { // オブジェクト初期化子を使って値を設定 Learn1_8 learn1_8 = new Learn1_8 { Talk = "テスト", Hp = 100, Speed = 5f }; Debug.Log (learn1_8.Talk); Debug.Log (learn1_8.Hp); Debug.Log (learn1_8.Speed); } } |
終わりに
わたくしがC#の勉強に使っている本は
です。
プログラミングの基礎を勉強した人向けですが、プログラミングの定石について学習する事が出来ます。
JavaやJavaScriptを多少勉強したことはあるんですが、C#は今回が初めてです。
C#はJavaとも多少違いますね。
現状のJavaがどうなっているかはわかりませんが・・・・(^_^;)
今回はとりあえずクラスの定義といった基本的な事を学習しました。
んー・・・全然ゆるい感じのカテゴリになってない・・・・。