はじめに
Unityを始めるにあたりそもそもC#も触っていなかったので基本的な言語仕様や構文等のスタディの備忘録を付けておきます。 C#固有、Unity固有の何かがあればそれもメモしていきます。
- はじめに
- 基本のデータ型
- var
- foreach
- 配列
- 可変長配列
- 連想配列
- define定義
- if文(制御構文)
- swithc文
- C#におけるクラス
- 構造体
- インターフェイス
- キャスト、型情報の利用
- パーシャルクラス、パーシャルメソッド
- 拡張メソッド
- リフレクション
- ジェネリック
- デリゲート
- イベント
- ラムダ式
- LINQ
- コルーチン
- デザインパターン
- コーディング規約
- 参考
基本のデータ型
型 | 幅 | 範囲(ビット) | |
---|---|---|---|
byte | 符号なし整数 | 8 | 0 ~ 255 |
sbtyte | 符号付き整数 | 8 | -128 ~ 127 |
int | 符号付き整数 | 32 | -2147483648 ~ 2147483647 |
uint | 符号なし整数 | 32 | 0 ~ 4294967295 |
short | 符号付き整数 | 16 | -32768 ~ 32767 |
ushort | 符号なし整数 | 16 | 0 ~ 65535 |
long | 符号付き整数 | 64 | -922337203685477508 ~ 922337203685477507 |
ulong | 符号なし整数 | 64 | 0 ~ 18446744073709551615 |
float | 単精度浮動小数点型 | 32 | -3.402823e38 ~ 3.402823e38 |
double | 倍精度浮動小数点型 | 64 | -1.79769313486232e308 ~ 1.79769313486232e308 |
char | 単一Unicode文字 | 16 | テキストで使用される Unicode 記号 |
bool | 論理ブール型 | 8 | true または false |
object | 他すべてのの型の基本型 | ||
string | 文字列 |
例)
public class Hoge { // コンストラクタ. public Hoge() { Debug.Log("### 基本のデータ型\n"); byte by = 0; sbyte sby = 0; int i = 0; uint ui = 0; short s = 0; ushort us = 0; long l = 0; ulong ul = 0; float f = 0.0f; double d = 0.0; char c = 'a'; bool b = true; string str = "aaa"; Debug.Log(" byte : " + by + "\n"); // 書式指定での出力. Debug.Log( string.Format(" uint : 0x{0:X}\n", ui ); // unity4.6まで. Debug.LogFormat(" uint : 0x{0:X}\n", ui ); // unity5から. // {0}は引数の番号?. // 可変引数で複数の引数がある場合は{0},{1}で指定していく?. Debug.LogFormat(" int:[{0}] uint:[{1}]\n", i, ui ); } }
var
auto変数みたいなもの? メソッドスコープ内で宣言される変数に使用することができる。
func() { // string型で宣言しなくてもstringとして扱われる. var name = "山田太郎"; }
MS的には右辺の代入の型が明示的な場合は極力varを使うことをMSDNのコーディング規約にも書かれている。
foreach
C#でのforeachの書き方。
int[] = new int[10]; foreach(int v in array) { Debug.LogFormat(" {0}", v); } // varでも書ける. foreach(var v in array) { Debug.LogFormat(" {0}", v); }
配列
func() { // ↓NG // C,C++の様な書き方. int arry[ 10 ]; // ↓OK int[] arry = new int[10]; // 初期値を入れる. int[] arry = new int[10]{1,2,3,4,5,6,7,8,9,10}; // ↓NG // サイズが指定されている場合は要素数を入れないとエラーになる. int[] arry = new int[10]{1,2,3,4,5}; // サイズが無ければ初期化の要素数が配列の要素数となる. int[] arry = new int[]{1,2,3,4,5} arry[ 0 ] = 1; for( int i = 0; i < arry.Lehgth; i++ ) { Debug.LogFormat(" [{0}]:{1}\n", i, arry[i]); } // foreachでの書き方. foreach( int v in arry ) { Debug.LogFormat(" {0}\n", v ); } }
可変長配列
STLのvectorみたいなものです。 List<>というのがあります。 これはC#で用意されているものです。
using System.Collections.Generic; // List<>のnamespace func() { List<int> list = new List<int>(); // 追加. list.Add(10); list.Add(20); list.Add(30); list.Add(40); // 挿入. list.Insert(2, 100); Debug.LogFormat("count : [{0}]\n", list.Count); // 配列の様にアクセスできるみたい... for( int i = 0; i < list.Count; i++ ) { Debug.LogFormat(" [{0}] : [{1}]\n", i, list[i]); } foreach( int v in list) { Debug.LogFormat(" [{0}]\n", v); } // sort. list.Sort(); foreach( int v in arry ) { Debug.LogFormat(" [{0}]\n", v ); } }
連想配列
STLのmapの様なものです。 Dictionary<>というのがあります。 keyとvalを指定して指定したkeyでvalを引っ張ってくることができます。
using System.Collections.Generic; func() { Dictionary<int,string> dic = new Dictionary<int,string>(); dic.Add(1,"Hoge1"); dic.Add(2,"Hoge2"); dic[3] = "Hoge3"; Debug.LogFormat(" val:[{0}]", dic[1]); // キーの列挙. foreach(var key in dic.Keys) { Debug.LogFormat(" key:[{0}]", key); } // 要素の列挙. foreach(var val in dic.Values) { Debug.LogFormat(" val:[{0}]", val); } // キーと要素の列挙. foreach(KeyValuePair<int,string> pair in dic) { Debug.LogFormat(" [{0}] : [{1}]", pair.Key, pair.Value); } }
define定義
・[Unity] 全体に反映されるマクロ(シンボル)を定義する
・プラットフォーム依存コンパイル Unityで用意されているプラットフォームごとの#defineディレクティブ。
“#if"のディレクティブは使えるらしい…
“#define"定義はファイルの先頭で行わないとダメ?
using System.Collections; using System.Collections.Generic; using UnityEngine; #define _ENABLE_HOGE public class Main : MonoBehaviour {
以下の様なエラーが出ました。
Assets/Scripts/Main.cs(6,0): error CS1032: Cannot define or undefine preprocessor symbols after first token in file
ファイルの先頭に持っていったらエラーは出なくなりました。
#define _ENABLE_HOGE using System.Collections; using System.Collections.Generic; using UnityEngine; public class Main : MonoBehaviour {
if文(制御構文)
C#ではif文は必ずbooleanになっていないとダメです。
enum Attr { None = 0, Fire = 0x1 << 0, // 火属性. Water = 0x1 << 1, // 水属性. Earth = 0x1 << 2, // 土属性. Light = 0x1 << 3, // 光属性. Dark = 0x1 << 4, // 闇属性. } func() { int i = 0; // ↓ダメ. // 数値ではエラーになる. if( i ) { } // ↓OK if( i == 0 ) { } // ビット演算だと判定が冗長になってしまう... Attr attr = Attr.None; attr = Attr.Fire; // ↓NG // ビット演算ではboolにならない. if( attr & Attr.Fire ) { } // ↓OK if( (attr & Attr.Fire) == Attr.Fire ) { Debug.Log("### 火属性.\n"); } attr = Attr.Water | Attr.Light; if( (attr & (Attr.Water | Attr.Light)) == (Attr.Water | Attr.Light) ) { Debug.Log("### 水&光属性.\n"); } // 良いかどうかは置いておいて次の様な書き方でも判定できる. if( (attr & (Attr.Water | Attr.Light) != 0 ) { Debug.Log("### 水&光属性.\n"); } }
・【Unity】HasFlag 関数を Unity でも使用できるようにする拡張メソッド
swithc文
func() { int val = 1; switch(i) { case 0: break; case 1: break; default: break; } }
文字列も値として扱うことができる。
func() { string val = "TEST001"; switch(val) { case "TEST001": break; case "TEST002": break; default: break; } }
C#におけるクラス
クラスがメンバーに持てるもの
フィールド 変数。 通常あまり生のメンバー変数を公開するものではないが、Unityの場合パブリックフィールドにしないとインスペクターに表示されない。
メソッド/コンストラクタ/デストラクタ 関数。 コンストラクタとデストラクタは少し特殊な関数。
プロパティ アクセサの簡易表記法。
インデクサー オブジェクトを配列のように扱えるようにするための機能。
イベント 処理の一部を外部に委譲したり、何かのタイミングを通知するための機能。
コンストラクタ オブジェクトの初期化処理を行うためのメソッド。 ただし、UnityはMonoBehaviourを継承したクラスがコンストラクタを実装することを推奨していません。 Awake/Startを使いましょう。
デストラクタ オブジェクトの破棄処理を行ためのメソッド。 C#ではGCが動いていて明示的な破棄はしなくてよいので、通常は意識する必要はありません。 C#で破棄のタイミングを管理したいクラスはIDisposableを継承して、Dispose()メソッドを実装し、using文と合わせて使います。 ただ、Dispose()の実装は慣れていないと難しい…
クラスメンバーとインスタンスメンバー
クラス修飾子 ・sealed sealed修飾子を付けたクラスは継承できなくなります。
メンバーフィールド修飾子 ・const ・readonly
メンバーメソッド修飾子 ・virtual ・override ・extern
属性(アトリビュート)
自分でクラスを定義してみます。
public class Hoge { public Hoge() { Debug.Log("### Hoge::Hoge()"); } // デストラクタ. // publicを付けるとビルドエラーになる. ~Hoge() { Debug.Log("### Hoge::~Hoge()"); } public int x = 0; public void SetY(int i) { y = i; } public int GetY() { return y; } private int y = -1; private int z = 0; } func() { Hoge hoge = new Hoge(); hoge.SetY(10); Debug.LogFormat(" x:[{0}]", hoge.x); Debug.LogFormat(" z:[{0}]", hoge.GetY()); }
Unityのinspectorに表示してみます。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class TestAttribute : MonoBehaviour { // publicはinspectorに表示される. public int test_int = 10; // 初期値も反映される. public float test_float = 20.0f; public string test_string = "test"; public GameObject test_gameobject; public ElementAttr test_enum; // privateは表示されない. private int _test_int; // Use this for initialization void Start () { } // Update is called once per frame void Update () { } }
- 表示結果
変数名の先頭の小文字は勝手に大文字に変換されるようです。
プロパティ
public class Hoge { // プロパティ. // 自動プロパティ. public int X { get; set; } public int Z { // setアクセサ(setterとも言う). set { // valueという名前の変数に代入された値が入る. if(!(value >= 0 && value <= 10)) { Debug.Assert(false); return; } z = value; } // getアクセサ(getterとも言う). get { return z; } } private int z = 0; } func() { Hoge hoge = new Hoge(); // プロパティ. hoge.X = 100; Debug.LogFormat(" X:[{0}]", hoge.X); // エラーが拾える. hoge.Z = 100; Debug.LogFormat(" Z:[{0}]", hoge.Z); hoge.Z = 10; Debug.LogFormat(" Z:[{0}]", hoge.Z); }
属性(アトリビュート)
属性(attribute)とはクラスやメンバーに追加情報を与えるもの。
Unityで使う場合はこちらを参考に。 ・UnityのAttribute(属性)についてまとめてメモる。 ・Unity/C# - Inspector表示の変更①
using System.Collections; using System.Collections.Generic; using UnityEngine; public class TestAttribute : MonoBehaviour { // publicはinspectorに表示される. public int test_int = 10; // 初期値も反映される. public float test_float = 20.0f; public string test_string = "test"; public GameObject test_gameobject; public ElementAttr test_enum; [Range(0.0f, 10.0f)] public float test_float_range = 5.0f; public float test_float2; // ← ここにはattributeは適用されない. [Header("----")] // ↓privateもinspectorに表示することができる. [SerializeField] private int test_int_private; [Multiline(2)] public string test_multiline; [TextArea(2, 5)] public string test_textarea; // privateは表示されない. private int _test_int; // Use this for initialization void Start () { } // Update is called once per frame void Update () { } }
- 表示結果
構造体
public struct Point { public int x; public int y; public Point(int x, int y) { this.x = x; this.y = y; } } func() { Point point = new Point(10, 20); Debug.LogFormat(" x:[{0}] y:[{1}]"); }
インターフェイス
記述中…
キャスト、型情報の利用
- キャスト
TestClass test = (TestClass)obj;
- as演算子
TestClass test = obj as TestClass;
キャストとは逆に「変換先の型」が右側に来ます。 参照型にしか使用できません。 値型(struct等)には使用できません。 変換できなかった場合はnullが返ります。
キャストを試してみます。
func() { int i; float f; // OK. f = i; // ↓コンパイルエラーになる. i = f; // 明示的なキャストが必要. i = (int)f; }
Assets/Scripts/Main.cs(453,8): error CS0266: Cannot implicitly convert type `float' to `int'. An explicit conversion exists (are you missing a cast?)
public class A {} public class B {} public class D : B {} public interface I1 {} public interface I2 {} public class E : I1, I2 {} { D d = new D(); B b = d; // キャストは不要. } { E e = new E(); I1 i1 = e; // キャストは不要. I2 i2 = e; } { B b = new D(); D d = (D)b; // 明示的なキャストが必要. } { A a = new A(); B b = (B)a; // コンパイルエラー. }
Assets/Scripts/Main.cs(490,13): error CS0030: Cannot convert type `Main.A' to `Main.B'
as演算子を試してみます。
{ A a = new A(); // ↓これだとコンパイルエラーになる. //B b = a as B; // object型にキャストしてからだと大丈夫. B b = (object)a as B; if(b == null) { // 変換できないとnullが返る. } }
{ I1 i1 = new E(); I2 i2 = i1 as I2; E e = i2 as E; }
パーシャルクラス、パーシャルメソッド
記述中…
拡張メソッド
既存のクラスに機能を追加する方法です。
public static class 適当なクラス名 { public static void Method( this 拡張したいクラス self ) { } }
例えばstringに拡張メソッドを追加する場合は以下の様に記述します。
public static class StringExt { public static void ExtMethod( this string self ) { } } func() { string str; str = "aaa"; str.ExtMethod(); }
リフレクション
記述中…
ジェネリック
いわゆるテンプレート。 List<>もDictionay<>もジェネリックで記述されている。
// ジェネリッククラス. class GenericClass<T> { T item; public void SetItem(T v) { item = v; } public T GetItem() { return item; } } func() { // ジェネリック. GenericClass<int> i = new GenericClass<int>(); i.SetItem(5); Debug.LogFormat(" i:[{0}]", i.GetItem()); GenericClass<float> f = new GenericClass<float>(); f.SetItem(10.0f); Debug.LogFormat(" f:[{0}]", f.GetItem()); }
デリゲート
記述中…
イベント
記述中…
ラムダ式
記述中…
LINQ
記述中…
コルーチン
記述中…
デザインパターン
記述中…
コーディング規約
記述中…
参考
・MSDN : C# プログラミング ガイド ・MSDN : C# のコーディング規則 (C# プログラミング ガイド) ・MSDN : ジェネリック型の型パラメーター (C# プログラミング ガイド) ・MSDN : 名前に関するガイドライン