ゲーム業界エンジニアの独り言

クライアント、サーバ、インフラなどなど興味あることなんでも紹介

C#機能チートシート

C++経験者がC#を書くためのチートシート(自分用) Unityを意識してるので基本的にはC#ver4まで対応

独習C# 第3版

独習C# 第3版

オブジェクトの寿命

オブジェクトは、誰からも参照されなくなったらガベージ コレクションの対象になる この時点をもって、オブジェクトの寿命は尽きる

  • 何もしなければ識別子のスコープを抜けた時点で参照が外れる
  • 明示的に別の値やnullを代入すればその時点で参照が外れる
using System;

class Sample
{
    public Sample() {}
    ~Sample() {}
}

public class Program
{
    public static void Main()
    {
        {
            var s = new Sample();

            // スコープ内なので GC しても生きてる
            GC.Collect();

            // 別の値の代入でも参照が外れるので s のインスタンスは寿命迎える
            // s = NULL;
            // GCすると回収される
            // GC.Collect();
        }

        // スコープから外れたので s のインスタンスは寿命迎える
        // GCすると回収される
        GC.Collect();
    }
}

値型と参照型

C#の型には大きく分けて値型、参照型のタイプがある

値型と参照型の違い

値型 = その型の値を直に保持

// 構造体は値型
struct Point
{
  public int x, y;

  public Point(int x, int y) { this.x = x; this.y = y; }
}

class Program
{
  static void Main()
  {
    Point a = new Point(12, 5);
    Point b = a; // a を複製して b に代入
    b.x = 0; // a.x は 12 のまま変化無し
  }
}

参照型 = 値の参照を保持

// クラスは参照型
class Point
{
  public int x, y;

  public Point(int x, int y){ this.x = x; this.y = y; }
}

class Program
{
  static void Main()
  {
    Point a = new Point(12, 5);
    Point b = a; // b は a の参照
    b.x = 0; // b.x と a.x も 0
  }
}

値型

  • ユーザ定義構造体(struct)
  • 基本データ型(int, float, double, bool, ...)
  • 列挙型(enum

参照型

  • クラス(class)
  • インターフェース(interface)
  • デリゲート(デリゲート)
  • string
  • 配列

列挙型

特定の値しか取らない型を表現

enum Week
{
    Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
}

class Program
{
    static void Main()
    {
        Week today = Week.Monday;
        Console.WriteLine(today); // 列挙型の値を出力すると名前がそのまま表示される
    }
}
Monday

変数

型推論

var キーワードを用いて型を自動判別して暗黙的に型付けされたローカル変数を定義 あくまで型の自動判別であって、 任意の型の値を代入できる万能な変数を作れるわけではない したがって、初期値を伴わない宣言は(型の自動判別ができない)エラー

var n = 1;
var x = 1.0;
var s = "hoge";

var n; // エラー(初期値が必要)

匿名型

文字どおり名前の無い型(クラス) クラスを定義せずに、その場でインスタンスを生成できる 内部的には型が自動生成されている 自動設定された型のプロパティには set アクセサーは無いので読み取り専用になる

var name = new { FamilyName = "山田", FirstName="太郎"};
Console.WriteLine(name.FamilyName + " " + name.FirstName);
山田 太郎

他のクラスのプロパティを初期化子に渡す場合は「プロパティ名 =」の部分を省略できる (初期化子で渡したプロパティの名前がそのまま匿名クラスでも使われる)

struct A
{
  public int X { set; get; }
  public int Y { set; get; }
  public int Z { set; get; }
}

class Program
{
  static void Main(string[] args)
  {
    A a = new A { X = 0, Y = 1, Z = 2};
    var b = new { a.X, a.Y };
    //↑ new { X = a.X, Y = a.Y } と同じ意味。
  }
}

null 許容型

値型の型名の後ろに ? を付ける事で、元の型の値または null の値を取れる型になる

int? x = 123;
int? y = null;

// null 許容型は HasValue メソッドを持つ
if (y.HasValue())
    ; // 有効値だった場合の処理
else
    ; // null だった場合の処理

?? 演算

値が null かどうかを判別し、null の場合には別の値を割り当てる

int? z = x ?? y; // x が 有効値なら x、null なら y で初期化

dynamic

動的型付け変数を定義

dynamic dx = 1;

dynamic 型を使うことでダックタイピングが可能 同じ名前のプロパティやメソッドを持っているなら何でも同列に扱える

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(Convert(new Point3D { X = 1, Y = 2, Z = 3 }));
        Console.WriteLine(Convert(new { X = 1, Y = 2 }));
        Console.WriteLine(Convert(new { X = 1, Y = 2, Z = 3 }));
    }

    static Point2D Convert(dynamic obj)
    {
        return new Point2D
        {
            // 型やインターフェイスに依存しない
            // obj が x と y を持っていれば良い
            X = obj.X,
            Y = obj.Y,
        };
    }
}

型情報の取得

静的な型の情報(名前)は typeof 演算子を用いて取得することができる

typeof(クラス名)

動的な型の情報(名前)は GetType メソッドを用いて取得することができる

変数名.GetType()

文字列

文字列連結

文字列に対して + 演算子を用いることで文字列の連結を行える

string str = "abc" + "def";
Console.WriteLine(str);
abcdef

文字列挿入

指定された形式に基づいてオブジェクトの値を文字列に変換し、別の文字列に挿入

int x = 1;
int y = 2;
var formatted = string.Format("({0}, {1})", x, y);
// var formatted = $"({x}, {y})"; // C#ver6以降はもっと楽に書ける
Console.WriteLine(formatted);
(1, 2)

書式指定

int x = 1;
int y = 2;
var formatted = string.Format("({0:0000}, {1:0000})", x, y);
Console.WriteLine(formatted);
(0001, 0002)

文字列から値型への変換

string str = "123456789";
int num = int.Parse(str); // 変換失敗時は例外発生

逐語的文字列リテラル

'' や "" の前に @を付けると \ とそれに続く文字がエスケープシーケンスとはみなされず、 普通に \ 記号として解釈される

string path = @"C:\windows\system"; 

string multiLineStr =
@"@-quoted string では、
文章を複数行に渡って書くことができる
";

var s = @"
var s = ""here 文字列中の引用符""; // "を使いたい場合は、""というように、2つ並べる
";

配列

配列の宣言

new で配列の実体を作成

int[] a = new int[5];

暗黙的型付け配列

配列の初期化時の、「new 型名[]」の型名を省略することが可能 配列の型は、{} の中身から推論される

var a = new[] {1, 3, 5, 7, 9};

四角い多次元配列

「四角い多次元配列」は全ての行の列数が同じ

型名[,] 変数名 = new 型名[長さ1, 長さ2];

// 宣言時に値を初期化する場合
型名[,] 変数名 = new 型名[,] {
    {値1-1, 値1-2, .....},
    {値2-1, 値2-2, .....},
    .....
};
double[,] a = new double[,]{ // 3行2列の行列
    { 1, 2 }, 
    { 2, 1 }, 
    { 0, 1 }
};

for(int i=0; i<a.GetLength(0); ++i) // a.GetLength(0) は 行数
{
    for(int j=0; j<a.GetLength(1); ++j) // a.GetLength(1) は 列数
    {
        int value = a[i, j];
    }
}

配列の配列

「配列の配列」は各行で列数が異なっていても構わない

double[][] a = new double[][]{ // 3行2列の行列
    new double[]{1, 2},
    new double[]{2, 1},
    new double[]{0, 1}
};

for(int i=0; i<a.GetLength(0); ++i) // a.GetLength(0) は 行数
{
    for(int j=0; j<a.GetLength(1); ++j) // a.GetLength(1) は 列数
    {
        int value = a[i, j];
    }
}

コレクション

初期化子

配列と同じような初期化記法を、任意のコレクションクラスに対して行うことができる (内部的にはAddメソッドが呼ばれている)

List<int> list = new List<int> {1, 2, 3};

IDictionary<TKey,TValue> のような辞書クラスに対しても可能

var map = Dictionary<string, int>
{
    { "One", 1 },
    { "Two", 2 },
    { "Three", 3 },
    { "Four", 4 },
};

foreach 文

foreach を使うことでコレクションのすべての要素を1回ずつ読み出すことができる

foreach(型名 変数 in コレクション)
int[] array = new int[10]{ 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 };

foreach(int n in array)
{
  Console.WriteLine(n);
}

foreach 文は実際にはこのような形に展開されている IEnumerable インターフェースを介して要素へのアクセスを行っている

IEnumerator e = array.GetEnumerator();
while(e.MoveNext())
{
  型名 変数 = (型名)e.Current;
  文
}

イテレータ ブロック

foreach で利用可能なコレクションを返すメソッドやプロパティを簡単に実装できる yield return、yield break を含むブロックをイテレーター ブロックと言う

  • return の変わりに yield return というキーワードを使う
  • break の変わりに yield break というキーワードを使う

戻り値の型が以下のうちのいずれか ・System.Collections.IEnumerator ・System.Collections.Generic.IEnumerator ・System.Collections.IEnumerable ・System.Collections.Generic.IEnumerable

class Program
{
  // イテレーター ブロック、IEnubrable を実装するクラスを自動生成してくれる
  static public IEnumerable<int> FromTo(int from, int to)
  {
    while(from <= to)
    {
      // yield return 文が呼ばれるたびに、foreach で使われる値を1つ得る
      yield return from++;
    
  }

  static void Main(string[] args)
  {
    foreach(int i in FromTo(10, 20))
    {
      Console.Write("{0}\n", i);
    }
  }
}

for 文や while 文を使わず、ベタに yield return を並べても OK

static public IEnumerable GetEnumerable(int from, int to)
{
  yield return 1;
  yield return 3.14;
  yield return "文字列";
  yield return new System.Drawing.Point(1, 2);
  yield return 1.0f;
}

関数指向

デリゲート

デリゲートとは、メソッドを代入するための変数の型(関数ポインタに近い) メソッドを変数に代入したり、関数の引数や戻り値にすることができる 述語やイベントハンドラ等に利用する

delegate void SomeDelegate(int a);

class Program
{
    static void Main()
    {
        // SomeDelegate 型の変数にメソッドを代入
        SomeDelegate a = new SomeDelegate(A);

        a(10); // デリゲートを介してメソッドを呼び出す
    }

    static void A(int n)
    {
        Console.WriteLine(string.Format("called A({0})", n));
    }
}
called A(10)

インスタンス(非static)メソッドの場合

delegate void ShowMessage();

class Person
{
    string name;

    public Person(string name)
    {
        this.name = name;
    }

    public void ShowName()
    {
        Console.WriteLine(string.Format("name: {0}", this.name);
    }
};

class Program
{
    static void Main()
    {
      Person p = new Person("山田 太郎");

      // インスタンスメソッドを代入
      ShowMessage show = new ShowMessage(p.ShowName);

      show();
    }
}
name: 山田 太郎

複数のメソッドを代入すると全てのメソッドが呼び出される

Person p1 = new Person("山田 太郎");
Person p2 = new Person("山田 二郎");
Person p3 = new Person("山田 三郎");

// インスタンスメソッドを複数代入
ShowMessage show = new ShowMessage(p1.ShowName);
show += new ShowMessage(p2.ShowName);
show += new ShowMessage(p3.ShowName);

show();
name: 山田 太郎
name: 山田 二郎
name: 山田 三郎

匿名メソッド

デリゲートを使う際にメソッドを定義しなくても名前のないメソッドを記述できる

delegate void SomeDelegate(int a);

class Program
{
    static void Main()
    {
        // delegate (引数リスト){ メソッド定義 }
        SomeDelegate a = new SomeDelegate(delegate(int n) {
            Console.WriteLine(string.Format("called A({0})", n));
        }

        a();
    }
}

ラムダ式

匿名メソッドを簡単に書くことが出来る

// 引数リスト => 式
(int n) => { return n > 0; };

// 単文の場合には、{} と return を省略できる
(int n) => n > 0;

// 呼び立し元から引数の型を判別出来る場合は省略できる
n => n > 0;

// Func という名前のデリゲートが標準で用意されている
// わざわざデリゲートを定義する必要が無い
// Func <返り値, 引数1, ...>
Func<int, int> f = n => n > 0;
f(1);

参照渡し

メソッドを呼び出す際に値の参照情報を渡す メソッドの引数に ref を付けることでその変数は参照渡しになる

class Program
{
    static void Main()
    {
        int a = 1;
        Console.WriteLine(string.Format("{0}"), a);
        Test(ref a);
        Console.WriteLine(string.Format("{0}"), a);
    }

    static void Test(ref int a)
    {
        a = 2; // 値を書き換える
    }
}
1
2

出力引数

out を付けることで出力用の参照引数であることを明示する

class Program
{
    static void Main()
    {
        int a;
        Test(out a); // out を使った場合、変数を初期化しなくていい
    }

    static void Test(out int a)
    {
        a = 10; // out を使った場合、メソッド内で必ず値を代入する
    }
}

名前付き引数

関数呼び出し時に引数名と値を指定する事で任意の順番で引数を指定できる デフォルト引数で任意の箇所を省略可能にする事ができる

class Program
{
    static void Main()
    {
        int s1 = Sum(x: 1, y: 2, z: 3); // Sum(1, 2, 3);
        int s2 = Sum(y: 1, z: 2, x: 3); // Sum(3, 1, 2);
        int s3 = Sum(y: 1);             // Sum(0, 1, 0);
    }

    static int Sum(int x=0, int y=0, int z=0)
    {
        return x + y + z;
    }
}

可変引数

params というキーワードを使って可変個の引数を取るメソッドを定義することが出来る

class Program
{
    static void Main()
    {
        int a = 1, b = 2, c = 3, d = 4, e  = 5;
 
        // 自動的に配列を作って値を格納
        int max = Max(a, b, c, d, e);
    }

    static int Max(params int[] a)
    {
        int max = a[0];
        for(int i=1; i<a.Length; ++i)
        {
            if(max < a[i])
            max = a[i];
        }
        return max;
    }
}

オブジェクト指向

object型

C# では、基底クラスを指定せずに作成した型は全て自動的に object 型を継承される object 型には Equals、ToString などの機能がある

class Hoge
{
    // do something
}

class Program
{
    static void Main()
    {
        Hoge h1 = new Hoge{};
        Hoge h2 = new Hoge{};
        
        // ToString(型名を取得)
        Console.WriteLine(h1. ToString());
        // Equals(型の比較)
        Console.WriteLine(Object.Equals(h1, h2));
        // ReferenceEquals(参照の比較)
        Console.WriteLine(Object.ReferenceEquals(h1, h2));
    }
}
Hoge
true
false

単一継承

C#のクラス継承では、1つのクラスしか継承できない(多重継承を認めていない)

class Base1 { }
class Base2 { }
class Derived : Base1, Base2 { } // コンパイル エラー

仮想メソッド

仮想メソッド virtual 修飾子をつける オーバーライドする際には override を明示的に付ける必要がある

class Base
{
    public virtual void Test() { Console.WriteLine("Base.Test("); }
}

class Derived : Base
{
    public override void Test() { Console.WriteLine("Derived.Test("); }
}

抽象メソッド

抽象メソッド(C++での純粋仮想関数)は abstract 修飾子をつける

class Base
{
    public abstract void Test();
}

class Derived : Base
{
    public override void Test() { Console.WriteLine("Derived.Test("); }
}

抽象クラス

インスタンスを作成できない抽象クラス

// クラスの定義時に abstract 修飾子付ける
abstract class Person
{
    // do something
}

class Program
{
    static void Main()
    {
        Person b = new Person(); // ビルド エラー
    }
}

インターフェイス

  • メンバー変数を持つことが出来ない
  • static メソッドを持つことが出来ない
  • 宣言したメソッド・プロパティはすべて public abstract になる
  • 1つのクラスが複数のインターフェースを実装できる
interface インターフェース名
{
    // メソッド・プロパティの宣言
}

// インターフェースの実装はクラスの継承と同じ構文
class クラス名 : インターフェース名
{
    // クラスの定義
}
interface ISampleInterface
{
    void SampleMethod();

    string Name
    {
        get;
        set;
    }
}

interface Sample
{
    void ISampleInterface.SampleMethod()
    {
        // do something
    }

    string ISampleInterface.Name
    {
        get { return "foo"; }
        set { }
    }
}
interface IEquatable<T>
{
    bool Equals(T obj);
}

public class Car : IEquatable<Car>
{
    public string Make {get; set;}
    public string Model { get; set; }
    public string Year { get; set; }

    public bool IEquatable.Equals(Car car)
    {
        if (this.Make == car.Make &&
            this.Model == car.Model &&
            this.Year == car.Year)
        {
            return true;
        }
        else
            return false;
    }
}

プロパティ

クラス外部から見るとメンバー変数のように振る舞い、内部から見るとメソッドのように振舞う

アクセスレベル 型名 プロパティ名
{
    set
    {
        // setアクセサー(setter とも言う)
        //  ここに値の変更時の処理を書く。
    }
    get
    {
        // getアクセサー (getter とも言う)
        //  ここに値の取得時の処理を書く。
    }
}
class Complex
{
    // 実装は外部から隠蔽
    private double re; // 実部
    private double im; // 虚部

    // 実部の取得・変更用のプロパティ
    public double Re
    {
        set { this.re = value; }
        get { return this.re; }
    }
    // 自動プロパティ
    // 単純なアクセサの場合は get/set の中身の省略もできる
    // public double Re { get; set; }

    // 実部の取得・変更用のプロパティ
    public double Im
    {
        set { this.im = value; }
        get { return this.im; }
    }

    // 絶対値の取得用のプロパティ
    public double Abs
    {
        // 読み取り専用プロパティ(setブロック無し)
        get { return Math.Sqrt(re * re + im * im); }
    }
}

class Program
{
    static void Main()
    {
        Complex c = new Complex();
        c.Re = 4; // Reプロパティのsetアクセサ呼び出し
        c.Im = 3; // Imプロパティのsetアクセサ呼び出し
        Console.WriteLine(c.Re);  // Reプロパティのgetアクセサ呼び出し      
        Console.WriteLine(c.Im);  // Imプロパティのgetアクセサ呼び出し
        Console.WriteLine(c.Abs); // Absプロパティのgetアクセサ呼び出し
    }
}

インデクサー

ユーザー定義型のオブジェクトでも、 配列と同じように a[i] という形での要素の読み書きができる

アクセスレベル 戻り値の型 this[添字の型 添字]
{
    set
    {
        // setアクセサ
        //  ここに値の変更時の処理を書く
        //  添字が使える以外はプロパティと同じ
    }
    get
    {
        // getアクセサ
        //  ここに値の取得時の処理を書く
        //  添字が使える以外はプロパティと同じ
    }
}
class BoundArray
{
    int[] array;
    int lower;   // 配列添字の下限

    public BoundArray(int lower, int upper)
    {
        this.lower = lower;
        array = new int[upper - lower + 1];
    }

    public int this[int i] // [int i, int j] 複数の添字を可能
    {
        set { this.array[i - lower] = value; }
        get { return this.array[i - lower]; }
    }
}

class Program
{
    static void Main()
    {
        BoundArray a = new BoundArray(1, 9);

        for (int i = 1; i <= 9; ++i)
            a[i] = i;

        for (int i = 1; i <= 9; ++i)
            Console.WriteLine(a[i]);
    }
}

読取り専用の変数

const 以外に、もう1つ定数のようなものを実現する方法がある readonly というキーワードを用いて、読取り専用(read only)の変数を定義できる

const との違い

  • クラスのメンバー変数のみ
  • static の有無を変えられる
  • コンストラクタ内で値を書き換え可能
  • コンパイル結果は変数と同等
  • new 可能
class A
{
    // readonly 修飾子をつける
    readonly int num;

    public A(int num)
    {
      this.num = num; // コンストラクタ内では書き換え可能
    }

    public void Method(int num)
    {
      int x = this.num; // 読み取りは可能
      this.num = num;   // ビルド エラー(書き換え不可)
    }
}

継承禁止

クラス定義時に sealed をつけることで、 継承を禁止することができる

sealed class SealedClass { }

class Derived : SealedClass // ビルド エラー
{
}

静的クラス

静的メンバーしか定義できないクラスを作ることができる

// クラス定義時に static をつける
static class Math
{
    public static const float PI = 3.14f; 

    // sin x を求める関数。
    static double Sin(double x)
    {
        // do something
    }
}

静的コンストラクタ

静的フィールドの初期化には、通常のコンストラクターではなく、静的コンストラクターを使う static キーワードを付ける以外は通常のコンストラクターの定義の仕方は同じ 静的コンストラクターは1度だけ呼び出される(そのクラスのメンバーに初めてアクセスした時)

class Person
{
    string name; // 名前、インスタンス フィールド
    int age;     // 年齢、インスタンス フィールド

    static string scientificName; // 学名、静的フィールド

    // 通常のコンストラクター
    public Person(string name, int age)
    {
      this.name = name;
     this.age  = age;
    }
  
    // 静的コンストラクター
   // static キーワードを付ける
    static Person()
    {
      Person.scientificName = "Homo sapiens";
    }
}

拡張メソッド

拡張メソッドを使用すると、既存の型に変更を加えることはなく、型にメソッドを追加できる 拡張メソッドは、インスタンスメソッドと同じ形式で呼び出せすことができる

// クラスとメソッドを static にする
static class Extensions
{
    // メソッドの引数に this をつける
    // string クラスに Parse メソッドを追加
    public static int Parse(this string str)
    {
        return int.Parse(str);
    }
}
// これを
int x = int.Parse("1");
// こう呼び出せる
int x = "1".Parse();

列挙型 にも使用可能

enum Week
{
    Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
}

static class Extensions
{
    public static string AddDot(this Week w) {
        // . を付けて返すだけ
        return w.ToString() + ".";
    }
}
Week w = Tuesday;
Console.WriteLine(Week.Monday.AddDot());
Console.WriteLine(w.AddDot());

インターフェースにも使用可能

static class Extensions
{
  // IEnumerable インターフェイスに Duplicate メソッド追加
    public static IEnumerable<T> Duplicate<T>(this IEnumerable<T> list)
    {
        foreach (var x in list)
        {
          yield return x;
          yield return x;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        IEnumerable<int> data = new int[]{ 1, 2, 3 };

        // インターフェースに対してメソッドを追加できる
        data = data.Duplicate();

        foreach (var x in data)
          Console.WriteLine(x);
    }
}

is 演算子

オブジェクトと、指定した型との間に互換性があるかどうかをチェックする

class Base { }
class Derived1 : Base { }
class Derived2 : Base { }

class Sample
{
    public static void M(Base x)
    {
        if (x is Derived1)
        {
            // x のインスタンスが Derived1 だった場合
        }
        else if (x is Derived2)
        {
            // x のインスタンスが Derived2 だった場合
        }
    }
}

as 演算子

オブジェクトと、指定した型との間に互換性がある場合はダウンキャストを行う 互換性が無い場合にキャスト演算子は例外を発生させるが、as 演算子は null を返す

class Base { }
class Derived1 : Base { }
class Derived2 : Base { }

class Sample
{
    public static void M(Base x)
    {
        // x のインスタンスが Derived1 ならダウンキャストされる
        // x のインスタンスが Derived2 なら null が返る
        Derived1 d = x as Derived1;
    }
}

属性

属性とは

属性(attribute)とはクラスやメンバーに追加情報を与えるもの C# では自分で属性を定義し、クラスやメンバーに付加することができる 属性を用いることで、コンバイラに対する指示を行ったり、クラスに情報を残すことができる

属性の情報は、以下のような場面で使われる

  • 条件コンパイルなどの、コンパイラへの指示に使う(Conditional や Obsolete)
  • 作者情報などをメタデータとしてプログラムに埋め込む(AssemblyTitle など)
  • リフレクションを利用して、プログラム実行時に属性情報を取り出して利用する

属性の使用

属性は [] でくくり、 クラスやメンバーの前に付ける

[属性名(属性パラメータ)]
[DataContract]
class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

例として、Conditional 属性を使用 Conditional 属性は、特定の条件下でのみ実行されるメソッドを定義するために使用される

class Program
{
    static void Main()
    {
        double[] array = new double[] { 9, 4, 5, 2, 7, 1, 6, 3, 8 };
        BubbleSort(array);
        Output(array);
    }

    /// バブルソートを行う
    static void BubbleSort(double[] array)
    {
        int n = array.Length - 1;

        for (int i = 0; i < n; ++i)
        {
            for (int j = n; j > i; --j)
                if (array[j - 1] > array[j])
                    Swap(ref array[j - 1], ref array[j]);

            IntermediateOutput(array); // ソートの途中段階のデータを表示
        }
    }

    static void Swap(ref double x, ref double y)
    {
        double tmp = x;
        x = y;
        y = tmp;
    }

    /// 配列の内容をコンソールに表示する
    static void Output(double[] array)
    {
        foreach (double x in array)
        {
            Console.Write("{0} ", x);
        }
        Console.WriteLine("\n");
    }

    /// SHOW_INTERMEDIATE というシンボルが定義されているときのみ
    /// 配列の内容をコンソールに表示する
    [Conditional("SHOW_INTERMEDIATE")]
    static void IntermediateOutput(double[] array)
    {
        Output(array);
    }
}

, で区切るか、複数の [] を並べることで複数の属性を指定することができる

[Conditional("DEBUG"), Conditional("TEST")]
void DebugOutput(string message)

[Conditional("DEBUG")]
[Conditional("TEST")]
void DebugOutput(string message)

定義済みの属性

標準ライブラリによって提供されている定義済み属性 コンパイラへの指示になっていて、コンパイル結果に影響を及ぼす

属性名 効果
System.AttributeUsageAttribute 属性の用途を指定(属性を自作する場合に使用)
System.ObsoleteAttribute 時代遅れなコードであることを示す(コンパイラが警告を発する)
System.Diagnostics.ConditionalAttribute 特定の条件下でのみ実行されるメソッドを定義

属性の対象

属性を付ける場所によって属性の対象は変わる

[assembly: AssemblyTitle("Test Attribute")] // プログラムそのものが対象
 
[Serializable] // クラスが対象
public class SampleClass
{
    [Obsolete("時期版で削除予定")] // メソッドが対象
    public void Test([In, Out] ref int n) // 引数が対象
    {
        n *= 2;
    }
}

しかし、属性を付ける位置によっては属性の対象が曖昧になることがある 曖昧さを解決するため、 明示的に属性の対象を指定する構文がある

[属性の対象 : 属性名(属性のオプション)]
[method: DllImport("msvcrt.dll")]
[return: MarshalAs(UnmanagedType.I4)]
public static extern int puts(
    [param: MarshalAs(UnmanagedType.LPStr)] string m);
対象名 説明
assembly アセンブリ(プログラムの実行に必要なファイルまとめた物)が対象
module モジュール(1つの実行ファイルやDLLファイルのこと)が対象
type クラスや構造体、列挙型やデリゲート(後述)等の型が対象
field フィールド(要するにメンバー変数のこと)が対象
method メソッドが対象
event イベント(後述)が対象
property プロパティが対象
param メソッドの引数が対象
return メソッドの戻り値が対象

属性の自作

System.Attribute クラスを継承することで新しい属性を自作することができる

// クラスの作者を記録しておくための属性 Author
// AttributeUsage 属性はその属性の用途を指定することが出来る
// この例だとクラスまたは構造体にのみ適用できる属性になる
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class AuthorAttribute : Attribute
{
    private string Name;       // 作者名
    public string Affiliation; // 作者所属
    public AuthorAttribute(string name) { this.Name = name; }
}
[Author("山田 太郎")]
class Test
{
  // 中身は省略
}

属性情報の取得

リフレクション機能を用いて属性情報を取得することができる

[Author("山田 太郎")]
[Author("山田 二郎")]
class AuthorTest
{
    [Author("山田 三郎")]
    public static void A() { }
    [Author("山田 三郎")]
    public static void B() { }
}
 
class Program
{
    static void Main()
    {
        GetAllAuthors(typeof(AuthorTest));
    }
 
    static void GetAllAuthors(Type t)
    {
        // クラスの Author
        GetAuthors(t);
 
        foreach (MethodInfo info in t.GetMethods())
        {
            // メソッドの Author
            GetAuthors(info);
        }
    }
 
    static void GetAuthors(MemberInfo info)
    {
        Attribute[] authors = Attribute.GetCustomAttributes(
            info, typeof(AuthorAttribute));
        foreach (Attribute att in authors)
        {
            AuthorAttribute author = att as AuthorAttribute;
            if (author != null)
            {
                Console.WriteLine(author.Name);
            }
        }
    }
}

参考サイト

++C++; // 未確認飛行 C

【Docker】FluentdとElasticsearchとKibanaの環境構築

FluentdとElasticsearchとKibanaの組み合わせが流行ってる?みたいなのでdocker環境で作ってみました。

前提

  • ログは行にJSON
  • FluentdとElasticsearchとKibanaは同じサーバ上に存在

構成

.
├── docker-compose.yml
├── elasticsearch
│   └── Dockerfile
├── fluentd
│   ├── Dockerfile
│   ├── fluent.conf
│   └── plugins
└── log
    └── hoge_log # 収集するログ

Docker

elasticsearch:
  build: elasticsearch
  ports:
    - 9200:9200
fluentd:
  build: fluentd
  ports:
    - 24284:24284
  volumes:
    - ./log:/var/log/hoge
  links:
    - elasticsearch
kibana:
  image: kibana
  ports:
    - 9204:5601
  environment:
      - ELASTICSEARCH_URL=http://elasticsearch:9200
  links:
      - elasticsearch

Fluentd

FROM fluent/fluentd:latest-onbuild

USER fluent

WORKDIR /home/fluent
ENV PATH /home/fluent/.gem/ruby/2.3.0/bin:$PATH
RUN gem install fluent-plugin-secure-forward # セキリュティ対策
RUN gem install fluent-plugin-elasticsearch # Elasticsearch連携

EXPOSE 24284

CMD fluentd -c /fluentd/etc/fluent.conf -p /fluentd/plugins -vv
<source>
  type tail
  path /var/log/hoge/hoge_log
  tag json.hoge
  pos_file /var/log/hoge/hoge_log.pos
  format json
</source>

<match json.**>
  type copy

  <store>
    type elasticsearch
    host elasticsearch
    port 9200
    logstash_format true
  </store>

  <store>
    type stdout
  </store>
</match>

Elasticsearch

FROM elasticsearch

RUN bin/plugin install mobz/elasticsearch-head

EXPOSE 9200

CMD  ["bin/elasticsearch", "-Des.insecure.allow.root=true"] # root=trueが無いと初回起動で失敗する

起動

$ docker-compose up --build

Elasticsearch ページ

http://[IP]:9200/_plugin/head/

Kibanaページ

http://[IP]:9204/

こんな感じで試しにログを追加してみる

$ echo "{ hoge :"hoge" }" >> log/hoge_log

Elasticsearchにログが入っていればOK

github

今回のプロジェクトを公開しています。 https://github.com/naruminn/fluentd-elasticsearch-kibana

Docker

Docker

【Docker】FluentdでとElasticsearchとRe:dashでログ管理

DockerでFluentdとElasticsearchとRe:dashを起動してログの収集と解析を行います。

Docker

dockerとdocker-composeを導入

CentOSであれば下記を参照 CentOS6.5にdockerとdocker-composeをインストール

前提

  • ログは行にJSON
  • Fluentd & Elasticsearchは同じサーバ上に存在

ログ例

{ "type": "chatlog", "name": "hoge1", "text": "hoge1" }
{ "type": "chatlog", "name": "hoge2", "text": "hoge2" }
{ "type": "chatlog", "name": "hoge3", "text": "hoge3" }
{ "type": "chatlog", "name": "hoge4", "text": "hoge4" }
{ "type": "chatlog", "name": "hoge5", "text": "hoge5" }

Fluentd & Elasticsearch

$ mkdir ~/workspace
$ cd ~/workspace

Fluentd

$ mkdir fluentd
$ cd fluentd
$ mkdir plugins
$ vi Dockerfile
$ vi fluent.conf
FROM fluent/fluentd:latest-onbuild

USER fluent

WORKDIR /home/fluent
ENV PATH /home/fluent/.gem/ruby/2.3.0/bin:$PATH
RUN gem install fluent-plugin-secure-forward
RUN gem install fluent-plugin-elasticsearch

EXPOSE 24284

CMD fluentd -c /fluentd/etc/fluent.conf -p /fluentd/plugins -vv
<source>
  type tail
  path /var/log/hoge/[log file] # 収集するログファイル名を指定
  tag json.hoge
  pos_file /var/log/hoge/[log file].pos # 収集するログファイル名を指定
  format json
</source>

<match json.**>
  type copy

  <store>
    type stdout
  </store>
  
  <store>
    type elasticsearch
    host localhost   # elasticsearchのホスト
    port 9200        # elasticsearchのポート
    logstash_format true
  </store>
</match>

Elasticsearch

$ mkdir elasticsearch
$ cd elasticsearch
$ vi Dockerfile
FROM elasticsearch

RUN bin/plugin install mobz/elasticsearch-head

EXPOSE 9200

CMD ["bin/elasticsearch", "-Des.insecure.allow.root=true"]

起動

$ cd ~/workspace
$ vi docker-compose.yml
elasticsearch:
  build: elasticsearch
  ports:
    - 9200:9200
fluentd:
  build: fluentd
  ports:
    - 24284:24284
  volumes:
    - [log folder]:/var/log/hoge # 収集するログフォルダを指定
$ docker-compose up

ElasticsearchのWebページ

http://[IP]:9200/_plugin/head/

Re:dash

詳しくは下記を参照 Re:dashをdockerで起動する

データソースを追加

Settings > DATA SOURCES > NEW DATA SOURCES

Base URLはこんな感じ

http://[IP]:9200

スクリーンショット 2016-07-22 12.23.42.png

Docker

Docker

CentOS6.5にdockerとdocker-composeをインストール

dockerインストール

$ sudo rpm -ivh http://ftp.riken.jp/Linux/fedora/epel/6/x86_64/epel-release-6-8.noarch.rpm
$ sudo yum update
$ sudo yum -y install docker-io

確認

$ docker --version

docker起動と自動起動

$ sudo service docker start
$ sudo chkconfig docker on 

docker-composeインストール

$ sudo sh -c "curl -L https://github.com/docker/compose/releases/download/1.5.2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose"
$ sudo chmod +x /usr/local/bin/docker-compose

root以外でも操作可能にする

$ groupadd docker
$ usermod -a -G docker [user]

Docker

Docker

Re:dashをdockerで起動する

dockerとdocker-composeが入っていればlinuxでもmacでも動くと思います。 (私はmacで起動しました) dockerの使い方と導入方法は説明しません。

Re:dash

公式サイト 公式セットアップ手順

クックパッド開発者ブログ ディレクターがSQLを使えてよかった話

この頃、社内ではre:dashというツールを各部署で使っています。re:dashでは、SQLで抽出したデータをそのままグラフにでき、任意の間隔で自動更新ができます。また、複数のグラフやデータを組み合わせてダッシュボードとしてまとめられます。 re:dashダッシュボードのサンプル 20160704110904.png

クックパッドさんのブログを見て導入してみました。 社内にSQL文化を作る良いきっかけになりそうです。

セットアップ

公式の手順通り 途中にエラーも出たけど起動に問題は無かった(たぶん・・・)

% git clone https://github.com/getredash/redash.git
% cd redash
% cp docker-compose-example.yml docker-compose.yml
% vi docker-compose.yml
# /opt/postgres-dataを自分の環境に合ったパスに変更
postgres:
  image: postgres:9.3
  volumes:
    - /opt/postgres-data:/var/lib/postgresql/data # ここ
% docker-compose up postgres
# postgresが無事起動したら終了
% ./setup/docker/create_database.sh
# 結果はエラーになるが特に問題は無かった
% docker-compose up # deamonにしたい場合は-d

Webページ

http://127.0.0.1:80
ID: admin
PASS: admin

ポートを変更したい場合はymlを修正

% vi docker-compose.yml
redash-nginx:
  image: redash/nginx:latest
  ports:
    - "80:80" # ここ
  links:
    - redash

データソースを追加

Webページから参照したDBの情報を設定(ここではMySQL

Settings > DATA SOURCES > NEW DATA SOURCES

スクリーンショット 2016-07-14 17.11.29.png

踏み台経由でDBに接続する場合はautosshが便利

# mac
% brew install autossh
# ubuntu
% apt-get install autossh
# autossh -f -M 0 -N -L [バインド]:[DBサーバ] [踏み台]
% autossh -M 0 -f -N -L 127.0.0.1:13306:127.0.0.1:3306 user@example.com

CLI

webページでのユーザー作成がメール認証だった redashのメール設定がうまく出来なかったのでCLIでユーザー作成

% docker-compose run redash bash
% cd /opt/redash/current
% ./bin/run ./manage.py users create [name] [mail] 

Docker

Docker

個人で広告収入型スマホゲームを作ってみたまとめ

個人で広告収入型スマホゲームを作りました。 その際に使用したゲームエンジン、ライブラリ、ツール、サイトをまとめます。 個人なのでお金は掛けられないので全てフリーなものを使用しています。 これから作りたい人たちの参考になれば嬉しいです。

ゲームライブラリ

言わずと知れたcocos2d-xを使用しました。バージョンはv3です。 http://jp.cocos.com/

リッチなスマホゲームを作る際の選択肢だと・・・ ・cocos2d-x ・Unity ・UnrealEngine あたりが選択肢になると思います。

私は2Dゲームを作りたかったのと、C++が得意だったのでcocos2d-xにしました。

UIエディターにはCocosStudioを使用しました。 cocos2d-xを使うならCocosStudioがスタンタードだと思います。

広告関連

AdMob

AdMobはgoogleが提供するアフィリエイト型の広告サービスです。 組み込むことで広告を表示できて広告がタップされると収入が得られます。 https://www.google.co.jp/admob/

ざっくり言うとスマホアプリでよくあるバナー広告とインタースティシャル広告が表示できます。 (インタースティシャル広告:ポップアップとして画面全体に表示される広告)

AdMobメディエーションが便利です。 https://support.google.com/admob/answer/3063564?hl=ja

AdMobメディエーションは、AdMob ネットワーク、サードパーティの広告ネットワーク、自社広告キャンペーンなど、複数の配信元の広告をアプリに配信できる機能です。この機能により、複数のネットワークに広告リクエストを配信して最適なネットワークを見つけることができるため、広告掲載率を最大化して収益アップを図ることができます。

私にゲームではAdMobとNendをAdMobメディエーションを使用して表示しています。 広告サービスを直接表示するので無くてAdMobメディエーションでラップさせれば、 最も収益が出るように複数の広告サービスから表示頻度の調整を自動でやってくれます。

Nend

http://nend.net/ こちらもAdMobと同じくアフィリエイト型の広告サービスです。 日本向けの広告でクリック単価が良いと評価されていたので選びました。

AdColony

http://video-ad.glossom.jp/

スマートフォン向け動画広告配信プラットフォーム「AdColony」 「AdColony」は、スマートフォンアプリのユーザーに対して、動画広告を自動的に配信するアドネットワークです。独自の動画再生技術により、瞬時に高画質なHD動画表示を実現しました。再生開始速度は0.1秒。 「AdColony」で新しい広告体験を。

アプリ内で動画を再生してユーザーが最後まで見た時点で収入が得られます。 私のゲームではこんな感じで使いました。

動画を見てくれたら良いものあげるよ!とユーザーに勧める ↓ ユーザーが動画を見る ↓ ゲーム内報酬をプレゼント

ユーザーが「もう一回やりたい!」とか「早くクリアしたい!」とか思うポイントに 組み込めばかなり有益な広告だと思います。

解析関連

GoogleAnalytics

https://www.google.com/intl/ja/analytics/ アプリ内に組み込めば多様な情報を取得できることができます。 ユーザ数や設定したイベントの送信件数などクラウド上で見ることが出来ます。 簡単に組み込めることが出来てかなり便利です。

GooglURLShortener

https://goo.gl/ アクセスログが取得可能な短縮URLQRコードを生成することが出来ます。

Chromeのアドオンもあります。 https://chrome.google.com/webstore/detail/googl-url-shortener/iblijlcdoidgdpfknkckljiocdbnlagk

アプリからTwitterFacebook、LINEなどのSNSにゲームのダウンロードリンクを ポストさせた際にどのリンクがどれくらい踏まれたのかを解析するために使用しました。

※追記 bit.ly

GoogleForm

https://www.google.com/intl/ja_jp/forms/about/

Google フォームを使用すると、イベントの計画、アンケートや投票の作成、学生への小テストの出題、さまざまな情報収集を簡単に、しかも効率的に行うことができます。フォームは、Google ドライブや既存のスプレッドシートから作成できます。スプレッドシートを使うとフォームへの回答を記録できます。

アンケートを作成する事が出来ます。 ゲームのユーザアンケートを作成、提供、解析するのに使えます。

宣伝関連

予約トップ10

https://yoyaku-top10.jp

予約トップ10では、App StoreGoogle playにリリースされる前の新作アプリの情報や人気アプリの新着イベント情報を公開しています。 予約トップ10は、無料でご利用いただけます。

事前登録サイトです。リリース前に人が集められるのは非常にありがたいです。 しかし、過激な内容だと審査で落とされます。(私のアプリは落ちました…)

レビューサイト

お金をかけずゲームを宣伝したいならメール依頼してレビューサイトに載せてもらうことです。 依頼してもスルーされることも多いので片っ端から依頼しましょう。

私はこんな感じのフォーマットでメール依頼しました。 アプリ詳細でストアをそのまま流用するのはNGみたいです。 レビューサイト側に魅力が伝わるような文章を新規で考える必要があります。

その他ツール類

ImageMagic

http://www.imagemagick.org/script/index.php

mageMagick(イメージマジック)は画像を操作したり表示したりするためのソフトウェアスイートである。GIF、JPEGJPEG 2000、PNG、PDF、Photo CD、TIFF、DPXなど100種類以上の画像ファイルフォーマットに対応している。GPL互換でより制限が緩い独自ライセンスが適用されている。 コマンドラインで画像フォーマット変換や、サイズ変換などの画像処理が出来ます。

私のゲームでは画像をPNGやJPGをWEBPに全て変換して使用しています。 WEBPはcocos2d-xで標準サポートでPNGやJPGよりファイルサイズを小さくすることが出来ます。

WebP(ウェッピー[2])は、米Googleが開発しているオープンな静止画フォーマット。ファイルの拡張子は「.webp」。 Googleの示した事例では、ファイルサイズは非可逆圧縮モードで(同一画像、同等画質の)JPEGと比較して25-34%小さくなり、可逆圧縮モードでPNGと比較して28%小さくなるとしている。また22%のファイルサイズ増加でアルファチャネルを追加できるとしている。可逆圧縮は、エンコードPNGよりも時間がかかるが、デコードはPNGよりも高速であると主張している[6]。一方、2013年10月に行われたMozillaの比較調査では、旧来のJPEGと大して変わらないという結果となった。[7]

lame

http://lame.sourceforge.net/

LAME (レイム)は、MP3への変換に用いられるフリー(GNU LGPL)のアプリケーションソフトウェア。名称は「LAME Ain't an MP3 Encoder」[1][2]の再帰的頭字語。1998年から開発が続けられている。 LAMEはMP3に変換するのに使われるソースコードの中でも特に優秀とされ、LAMEを採用しているエンコーダは他のエンコーダよりも品質のよいMP3ファイルを作ることができるため、広く支持されている。また、バージョン3.90からギャップレスを実現している[3]が、これを実現しているのはLAMEiTunesのみである[4]。

BGMや効果音の音楽ファイルをMP3に変換したり、 MP3のクオリティを下げてファイルサイズを小さくするために使用しています。 iTunesという選択肢もありますが、lameならコマンドラインで一括変換できるので便利です。

MP3Gain

http://www.forest.impress.co.jp/library/software/mp3gain/

 複数MP3ファイルの音量を、音質劣化なしに一定化できるソフト。

音楽ファイルの音量を一括で揃えるのに使用しています。

Cocos2d-x開発のレシピ

Cocos2d-x開発のレシピ

APIログをlogrotateのみで収集する

Webサーバでapacheなどが出力するAPIログを、定期的に収集する事はよくありますよね。 シェルを用意してcronで回すのも面倒だったので、logrotateのみを使って収集を行ってみました。

logrotate

logrotateは放っておけば際限なく肥大してしまう各種ログファイルに対して、 世代ローテーションを行いN世代以前の破棄や、転送など、様々な処理が行える。 世代管理やサイズ制限などの機構を自前で持たないプログラムからのログを管理するのに使用する。

環境

OS
CentOS release 6.5 (Final)

要件

  • 昨日のAPIのログをストレージサーバ(xxx.xxx.xxx.xxx)に転送する
  • ログは圧縮する
  • ログのローテートは一日ごと
  • ログのsuffixに日付を付ける
  • 7日以前のログはWEBサーバから削除する(容量対策)
対象ログ
/var/log/httpd/api_access_log
/var/log/httpd/api_error_log

API用のlogrotate設定ファイルを作成する

logrotate設定ファイル
/etc/logrotate.d/httpd-api

apacheをインストールすると自動でhttpdの設定ファイルが作成されます。 /etc/logrotate.d/httpd こちらにも設定が書いてあるので処理が被らないようにしましょう。 apacheのデフォルト設定が不要であればこちらを直接編集しても良いと思います。

/var/log/httpd/api_access_log {
    daily
    rotate 7
    dateext
    create 0644 apache apache
    olddir /var/backup/log/api
    missingok
    sharedscripts
    compress
    lastaction
        /bin/mv /var/backup/log/api/api_access_log-`date '+%Y%m%d'`.gz /var/backup/log/api/api_access_log-`date '+%Y%m%d' -d '1days ago'`.gz
    endscript
}

/var/log/httpd/api_error_log {
    daily
    rotate 7
    dateext
    create 0644 apache apache
    olddir /var/backup/log/api
    missingok
    sharedscripts
    compress
    postrotate
        /sbin/service httpd reload > /dev/null 2>/dev/null || true
    endscript
    lastaction
        /bin/mv /var/backup/log/api/api_error_log-`date '+%Y%m%d'`.gz /var/backup/log/api/api_error_log-`date '+%Y%m%d' -d '1days ago'`.gz
        /usr/bin/rsync -az  -e ssh /var/backup/log/api/ user@xxx.xxx.xxx.xxx:/var/backup/log/api/
    endscript
}
  • 昨日のログをgzip化してolddirに移動
  • ログファイル名の日付が今日になっているので昨日に変更
  • olddirをrsyncでストレージサーバに転送
  • access_logとerror_logのolddirは同じなのでrsyncは一回のみ

各パラメータの詳細はこのサイトが参考になります。

ログローテーション(logrotate)を使ってみる ( httpd(apache)の設定例 ) http://server-setting.info/centos/loglotation.html

エラーチェック

下記コマンドを叩いてエラーが出ない事を確認します。

# logrotate -dv /etc/logrotate.d/httpd

新しいLinuxの教科書

新しいLinuxの教科書