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

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

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