C#の初期化子(2)-オブジェクト初期化子

visual studio2019 C#8.0および一部C#7.3で動作確認。

【 目次 】

フィールド変数の初期化

オブジェクト初期化子(Object initializers)を使うと、クラスのインスタンスや構造体の生成時にpublicなフィールド変数やpublicなプロパティに初期値を代入する事ができる。

フィールドとかプロパティとかクラスのメンバー等のクラスの構成要素の用語の意味については

初期化するフィールド変数やプロパティのアクセス装飾子は、internalとかクラスの外部からアクセス可能な装飾子が必要。
以下にその例を示す。

以下のようなクラスを定義して

class CPerson1
{
    public string name;
    public int age;
    public void ShowMe() { Console.WriteLine($"CPerson1: name={name}, age={age}"); }
}

クラスの場合

CPerson1クラスを初期化する際にオブジェクト初期化子を使ってnameフィールドやageフィールドを初期化してその値をShowMeメソッドを使って表示してみると

var cperson1A = new CPerson1() { name = "CA", age = 12 };
cperson1A.ShowMe();

上記の1行目のコードは次のコードと等価

var cperson1A = new CPerson1();
cperson1A.name = "CA";
cperson1A.age = 12;

実行結果

CPerson1: name=CA, age=12

オブジェクト初期化子によってオブジェクトのメンバーに対する初期化を簡潔に記述できるのがわかる。

{}の前の()は省略して以下のように記述する事もできる。

var cperson1B = new CPerson1 { name = "CB", age = 12 };

また、次のように一部のフィールドのみを初期化する事もできる。

var cperson1C = new CPerson1 { name = "CC" };

構造体の場合

構造体の場合も同様で

struct SPerson1
{
    public string name;
    public int age;
    public void ShowMe() => Console.WriteLine($"SPerson1: name={name}, age={age}");
}

上記の構造体に対して

var sperson1A = new SPerson1() { name = "SA", age = 12 };
sperson1A.ShowMe();
var sperson1B = new SPerson1{ name = "SB", age = 12 };
sperson1B.ShowMe();
var sperson1C = new SPerson1{ name = "SC"};
sperson1C.ShowMe();

実行結果

SPerson1: name=SA, age=12
SPerson1: name=SB, age=12
SPerson1: name=SC, age=0

プロパティの初期化

前の例のフィールド変数をプロパティに変更した場合の例を示す。

クラスと構造体の定義

class CPerson2
{
    public string Name { get; set; }
    public int Age { get; set; }
    public void ShowMe() { Console.WriteLine($"CPerson2: Name={Name}, Age={Age}"); }
}
struct SPerson2
{
    public string Name { get; set; }
    public int Age { get; set; }
    public void ShowMe() { Console.WriteLine($"SPerson2: Name={Name}, Age={Age}"); }
}

オブジェクト初期化子を使ってNameプロパティやAgeプロパティを初期化してその値をShowMeメソッドを使って表示する

// Class
var cpersonA = new CPerson2 { Name = "CA2", Age = 12 };
cpersonA.ShowMe();
var cperson2B = new CPerson2 { Name = "CB2" };
cperson2B.ShowMe();
// struct
var spersonA = new SPerson2 { Name = "A", Age = 12 };
spersonA.ShowMe();
var spersonB = new SPerson2 { Name = "B" };
spersonB.ShowMe();

コンストラクタと名前付き引数による初期化

参考のために、オブジェクト初期化子の代わりにコンストラクタとメソッドの名前付き引数によって初期化をおこなう場合のコードを示す。

クラスの定義

struct SPerson0A
{
    private string name;
    private int age;
    public SPerson0A(string name,int age) { this.name = name; this.age = age; }
    public SPerson0A(string name): this(name,0) {}
    public SPerson0A(int age) : this("", age) { }
    public void ShowMe() { Console.WriteLine($"SPerson0A: name = {name}, age = {age}"); }
}

コンストラクタとthis キーワードについては。

このクラスを名前付き引数を使ってオブジェクト初期化子と似たような構文で初期化できる。

nameフィールドやageフィールドを初期化してその値をShowMeメソッドを使って表示

new CPerson0A(name : "A", age : 12).ShowMe();
new CPerson0A(name: "B").ShowMe();

名前付き引数については。

コンストラクタを使って初期化すると内部変数nameやageをクラスの外部からアクセスできないように隠蔽できるメリットがある。
しかし、nameとageの両方そしてnameやageのみを初期化する場合は、それぞれの場合においてコンストラクタを用意する必要があってコードが煩雑になってしまう。

コンストラクタとオブジェクト初期化子

オブジェクト初期化子を使ってオブジェクトを生成する時に()が省略できると述べたが、実は()を省略しても引数無しのコンストラクタが実行される。 また、()内に引数を指定して引数付きのコンストラクタの実行も可能。

クラスの定義

class CPerson3
{
    public string name;
    public int age;
    public CPerson3() {
        Console.WriteLine($"引数を持たないコンストラクタを実行");
    }
    public CPerson3(string s)
    {
        Console.WriteLine($"引数 {s} のコンストラクタを実行");
    }
    public void ShowMe() { Console.WriteLine($"SPerson1: name={name}, age={age}"); }
}

クラスのインスタンスの生成

// カッコを省略して引数を持たないコンストラクタを実行
new CPerson3 { name = "C", age = 12 }.ShowMe();
// 引数を持たないコンストラクタを実行
new CPerson3() { name = "C", age = 12 }.ShowMe();
// 引数を持つコンストラクタを実行
new CPerson3("xxx") { name = "C", age = 12 }.ShowMe();

実行結果

引数を持たないコンストラクタを実行
SPerson1: name=C, age=12
引数を持たないコンストラクタを実行
SPerson1: name=C, age=12
引数 xxx のコンストラクタを実行
SPerson1: name=C, age=12

カッコを省略した場合には、引数を持たないコンストラクタが呼び出されるのがわかる。

匿名型との比較

匿名型を使っても似たようなコードを記述できる。 これも参考のために、匿名型を使う例を以下に示す。

var anonymous = new { name = "匿名クラス", age = 17 };
Console.WriteLine($"name={anonymous.name}, age={anonymous.age}");

匿名型はクラスや構造体の定義を省略できるが、単純なクラスしか実現できないので活用の場面が限られる。

また、匿名型で初期化したメンバー変数は読み取り専用となるため初期化した後、値を変更する事はできない。
以下のコードはエラーになる。

anonymous.name = "変更できない";
anonymous.age++;

以下のようなエラーメッセージが

エラー CS0200 プロパティまたはインデクサー '.name' は読み取り専用であるため、割り当てることはできません

オブジェクト初期化子とインデクサによる初期化

オブジェクト初期化子にインデクサを指定する事もできる。

クラスの定義

public class Matrix
{
    protected double[,] storage = new double[3, 3];

    public double this[int row, int column]
    {
        // The embedded array will throw out of range exceptions as appropriate.
        get { return storage[row, column]; }
        set { storage[row, column] = value; }
    }
    virtual public void ShowMe() {
        for (int row = 0; row < storage.GetLength(0); ++row)
        {
            for (int column = 0; column < storage.GetLength(1); ++column)
            {
                Console.WriteLine($"Matrix[{row}, {column}] = {storage[row, column]}");
            }
        }
    }
}

クラスのインスタンスの生成

var identity = new Matrix
{
    [0, 0] = 1.0,
    [0, 1] = 0.0,
    [0, 2] = 0.0,

    [1, 0] = 0.0,
    [1, 1] = 1.0,
    [1, 2] = 0.0,

    [2, 0] = 0.0,
    [2, 1] = 0.0,
    [2, 2] = 1.0,
};
identity.ShowMe();

実行結果

Matrix[0, 0] = 1
Matrix[0, 1] = 0
Matrix[0, 2] = 0
Matrix[1, 0] = 0
Matrix[1, 1] = 1
Matrix[1, 2] = 0
Matrix[2, 0] = 0
Matrix[2, 1] = 0
Matrix[2, 2] = 1

複雑な例

以下の例はフィールド変数,プロパティ,インデクサメンバーをオブジェクト初期化子で初期化。

クラスの定義

public class MatrixEx : Matrix
{
    public int filed;
    public string PropStr { get; set; }
    override public void ShowMe()
    {
        base.ShowMe();
        Console.WriteLine($"filed = {filed}, PropStr = {PropStr}");
    }
}

インスタンスの生成

new MatrixEx()
{
    filed = 25,
    PropStr = "xxx",
    [0, 0] = 1.0,
    [0, 1] = 0.0,
    [0, 2] = 0.0,

    [1, 0] = 0.0,
    [1, 1] = 1.0,
    [1, 2] = 0.0,

    [2, 0] = 0.0,
    [2, 1] = 0.0,
    [2, 2] = 1.0,
}.ShowMe();

より複雑な例

public class Complicated
{
    public string name;
    private double _size;
    public double Size { set { _size = value; } }
    private int[] _iAry = new int[4];
    public char this[int i] { set { _iAry[i] = value; } }
    private Dictionary<string, string> _dict = new Dictionary<string, string>();
    public string this[char c, int i] { set { _dict.Add($"{c}, i",value); } }
    public void ShowMe()
    {
        Console.WriteLine($"name = {name}, Size = {_size}");
        for(int i=0; i< _iAry.Length; i++)
            Console.WriteLine($"this[{i}] = {_iAry[i]}");
        foreach (KeyValuePair<string, string> kvp in _dict)
        {
            Console.WriteLine($"this[{kvp.Key}] = {kvp.Value}");
        }
    }
}

インスタンスの生成

new Complicated
{
    name = "object one",
    [1] = '1',
    [2] = '4',
    [3] = '9',
    Size = Math.PI,
    ['C', 4] = "Middle C"
}.ShowMe();

参考記事

公式ドキュメント

オブジェクト初期化子は再帰的に書ける

  • 小ネタ オブジェクト初期化子 | ++C++; // 未確認飛行 C ブログ

    オブジェクト初期化子は再帰的に書けます。
    その場合、この例の x.A.X というように、全部展開されて、そこに代入が行われます。
    ここで注意が必要なのは、x.A の初期化は外からは行われないということです。
    もしも、Line のコンストラクター内で A を初期化していなければ、当然のようにぬるぽります。

ページのトップへ戻る