目次

概要

C# の文法では、メソッドやプロパティなどの抽象定義(abstract 修飾子が付いているもの)と自動実装(コンパイラーが具体的な実装を生成してくれるもの)の見た目が似ているため、 少し混乱しやすいです。

ポイント
  • 抽象定義 … インターフェイスのメンバーや抽象メソッドのように、宣言のみの(規約だけ定めて、実装を持たない)もの。

  • 具象定義 … 通常のメソッドやプロパティのように、実装まで書いてあるもの。

  • 自動実装 … プロパティとイベントは、実装を省略して、コンパイラーに自動生成してもらうことが可能。この際、抽象定義と見た目が近いので注意。

自動実装プロパティ

C# では、プロパティとイベントの場合、実装を省略して書くことで、コンパイラー任せで自動的に実装を作ることができます。 参考:

便利な機能ですが、抽象定義(abstract 修飾子が付いていたり、インターフェイス中に定義されるメンバー)の書き方と似ているため注意が必要です。

例えば、プロパティの場合、以下のようになります。

interface ISample
{
    // 抽象定義: これは宣言のみで実装を持たない(規約のみを定める)
    int A { get; set; }
}

abstract class Sample
{
    // 抽象定義: これも宣言のみで、実装を持たない
    public abstract int A { get; set; }

    // 具象定義: これは自動実装プロパティ(コンパイラー生成の実体を持つ)
    public int X { get; set; }
}

単純に、abstract が付いているか、もしくはインターフェイス内にあれば抽象定義(宣言)です。 その他の場合は自動実装プロパティになります。

ちなみに、X の自動実装の展開(コンパイラーによる自動生成)結果は以下のようになります。 (A の側は抽象定義なので、このような展開は起こりません。)

    public int X
    {
        get { return _X; }
        set { _X = value; }
    }

    // 実際には、プログラマーに見えない特殊な名前が与えられます
    private int _X;

自動的に生成されたフィールド(この例でいう _X)をバック フィールド(backing field)と呼びます。 通常、C# コードからバック フィールドは見えなくなっていますが、 「リフレクション」を使うことで覗き見ることができます。

using System;
using System.Reflection;

abstract class Sample
{
    public abstract int A { get; set; }
    public int X { get; set; }
}

class Program
{
    static void Main()
    {
        var fields = typeof(Sample).GetFields(BindingFlags.NonPublic | BindingFlags.Instance);

        foreach (var field in fields)
        {
            Console.WriteLine(field.Name);
        }
    }
}

結果として表示されるのは X プロパティのバック フィールドになります。 (繰り返しますが、A の方は抽象プロパティなので、同様のフィールドは生成されません。)

<X>k__BackingField

見ての通り、通常の C# コードからは定義できない名前(< から始まる名前)になっています。 (「IL」的には有効な名前です。)

自動実装イベント

プロパティは get; set; を明示的に書くだけまだましで、イベント構文はより一層、混乱を招きます。

interface ISample
{
    // 抽象定義: 宣言のみ、実装ない
    public event Action<int> A;
}

class Sample
{
    // 抽象定義: 宣言のみ、実装ない
    public abstract event Action<int> A;

    // 具象定義: 自動実装イベント
    public event Action<int> X;

    // ↑パッと見、デリゲート型のフィールドに見えるのがまた(別物です)
    Action<int> x;
}

プロパティ同様、abstract が付いているか、インターフェイス中で定義されている場合には抽象定義、それ以外は自動実装イベントです。

デリゲート型のフィールドと似て見える点にも注意が必要ですが、 実際には以下のようなアクセサーが自動生成されています。

    // 簡易版。本当はこれに、スレッド安全性の保証用コードが入る。
    public event Action<int> X
    {
        add { _X = (Action<int>)Delegate.Combine(_X, value); }
        remove { _X = (Action<int>)Delegate.Remove(_X, value); }
    }

    // バック フィールド
    // C# の文法上は認められていないのでここでは _X で代用したものの、
    // 実際にはイベントと同名のフィールド(この例の場合 X)が作られる。
    // (IL 的には OK。)
    private Action<int> _X;

イベントの場合、クラス内から普通のデリゲート型のフィールドのように扱えますが、 これは実際には、自動実装によって生成されたバック フィールドへのアクセスになります。

using System;

class Sample
{
    public event Action<int> X;

    public void RaiseX(int value)
    {
        var d = X; // 実はこの X は自動生成されたバック フィールドの X
        if (d != null) d(value);
    }
}

class Program
{
    static void Main()
    {
        var s = new Sample();
        s.X += x => { }; // この X はイベントの X
    }
}

実際、 「リフレクション」を使うことでバック フィールドを確認できます。

using System;
using System.Reflection;

abstract class Sample
{
    public abstract event Action<int> A;
    public event Action<int> X;
}

class Program
{
    static void Main()
    {
        var fields = typeof(Sample).GetFields(BindingFlags.NonPublic | BindingFlags.Instance);

        foreach (var field in fields)
        {
            Console.WriteLine(field.Name);
        }
    }
}

X とだけ表示されます(イベントと同名のフィールドが生成されている)。 (前述のプロパティの例と同様、A の方は抽象イベントなので、同様のフィールドは生成されません。)

X

更新履歴

ブログ