オブジェクト初期化子とコレクション初期化子 (C# プログラミング ガイド)

C# では、1 つの命令文でオブジェクトまたはコレクションをインスタンス化し、1 つのステートメントでメンバーを割り当てることができます。

オブジェクト初期化子

オブジェクト初期化子を使用すると、オブジェクトの作成時にアクセスできるフィールドまたはプロパティに、コンストラクターを呼び出して代入ステートメントを使用しなくても、値を割り当てることができます。 オブジェクト初期化子の構文では、コンストラクターの引数を指定することも、引数 (およびかっこ構文) を省略することもできます。 以下の例では、名前付きの型である Cat でオブジェクト初期化子を使用する方法と、パラメーターなしのコンストラクターを呼び出す方法を示します。 Cat クラスでの自動実装プロパティの使用に注意してください。 詳細については、「自動的に実装されるプロパティ」を参照してください

public class Cat
{
    // Automatically implemented properties.
    public int Age { get; set; }
    public string? Name { get; set; }

    public Cat()
    {
    }

    public Cat(string name)
    {
        this.Name = name;
    }
}
Cat cat = new Cat { Age = 10, Name = "Fluffy" };
Cat sameCat = new Cat("Fluffy"){ Age = 10 };

オブジェクト初期化子の構文では、インスタンスを作成できます。その後、新規作成されたオブジェクトは、割り当てられたプロパティと共に、割り当ての変数に代入されます。

オブジェクト初期化子では、フィールドとプロパティを割り当てることに加え、インデクサーを設定できます。 次の基底 Matrix クラスをご覧ください。

public class Matrix
{
    private 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; }
    }
}

次のコードで単位行列を初期化できます。

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,
};

アクセス可能なセッターが含まれるアクセス可能なインデクサーを、引数の数や種類と関係なく、オブジェクト初期化子で式の 1 つとして使用できます。 インデックス引数は代入の左側となり、値は式の右側となります。 たとえば、IndexersExample に適切なインデクサーがある場合、次の初期化子はすべて有効です。

var thing = new IndexersExample
{
    name = "object one",
    [1] = '1',
    [2] = '4',
    [3] = '9',
    Size = Math.PI,
    ['C',4] = "Middle C"
}

上記のコードをコンパイルするには、IndexersExample 型に次のメンバーを指定する必要があります。

public string name;
public double Size { set { ... }; }
public char this[int i] { set { ... }; }
public string this[char c, int i] { set { ... }; }

オブジェクト初期化子と匿名型

オブジェクト初期化子はどのようなコンテキストでも使用できますが、LINQ クエリ式では特に便利です。 クエリ式では、次の宣言に示すように、オブジェクト初期化子を使うことによってのみ初期化できる匿名型が頻繁に使われます。

var pet = new { Age = 10, Name = "Fluffy" };

匿名型を使うと、LINQ クエリ式の select 句で、元のシーケンスのオブジェクトを、値と形状が元のものとは異なる可能性があるオブジェクトに変換できます。 シーケンス内の各オブジェクトの情報の一部のみを格納したい場合があります。 次の例では、製品オブジェクト (p) に多くのフィールドとメソッドが含まれており、製品名と単価を含むオブジェクトのシーケンスを作成することにのみ関心があるとします。

var productInfos =
    from p in products
    select new { p.ProductName, p.UnitPrice };

次の例で示すように、このクエリが実行されるとき、productInfos 変数には、foreach ステートメントでアクセスできるオブジェクトのシーケンスが格納されています。

foreach(var p in productInfos){...}

作成される匿名型内の各オブジェクトには、2 つのパブリック プロパティがあります。これらのプロパティには、元のオブジェクトのプロパティまたはフィールドと同じ名前が付けられます。 匿名型を作成するときに、フィールドの名前を変更することもできます。次の例では、UnitPrice フィールドの名前を Price に変更しています。

select new {p.ProductName, Price = p.UnitPrice};

required 修飾子を持つオブジェクト初期化子

required キーワードを使用して、呼び出し元がオブジェクト初期化子を使用して、プロパティまたはフィールドの値を設定するよう強制できます。 必須のプロパティをコンストラクター パラメーターとして設定する必要はありません。 コンパイラは、すべての呼び出し元がこれらの値を初期化することを保証します。

public class Pet
{
    public required int Age;
    public string Name;
}

// `Age` field is necessary to be initialized.
// You don't need to initialize `Name` property
var pet = new Pet() { Age = 10};

// Compiler error:
// Error CS9035 Required member 'Pet.Age' must be set in the object initializer or attribute constructor.
// var pet = new Pet();

特に、管理するフィールドまたはプロパティが複数あり、それらをすべてコンストラクターに含めたくない場合は、オブジェクトが適切に初期化されていることを保証するのが一般的な方法です。

init 修飾子を持つオブジェクト アクセサー

init アクセサーを使用して、設計されたオブジェクトを誰も変更できないように制限できます。 これはプロパティ値の設定を制限するのに役立ちます。

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; init; }
}

// The `LastName` property can be set only during initialization. It CAN'T be modified afterwards.
// The `FirstName` property can be modified after initialization.
var pet = new Person() { FirstName = "Joe", LastName = "Doe"};

// You can assign the FirstName property to a different value.
pet.FirstName = "Jane";

// Compiler error:
// Error CS8852  Init - only property or indexer 'Person.LastName' can only be assigned in an object initializer,
//               or on 'this' or 'base' in an instance constructor or an 'init' accessor.
// pet.LastName = "Kowalski";

必須の init-only プロパティは、型のユーザーに対して自然な構文を許可しながら、不変構造体をサポートします。

クラス型のプロパティを持つオブジェクト初期化子

オブジェクトを初期化するときは、クラス型のプロパティに対する影響を考慮することが重要です。

public class HowToClassTypedInitializer
{
    public class EmbeddedClassTypeA
    {
        public int I { get; set; }
        public bool B { get; set; }
        public string S { get; set; }
        public EmbeddedClassTypeB ClassB { get; set; }

        public override string ToString() => $"{I}|{B}|{S}|||{ClassB}";

        public EmbeddedClassTypeA()
        {
            Console.WriteLine($"Entering EmbeddedClassTypeA constructor. Values are: {this}");
            I = 3;
            B = true;
            S = "abc";
            ClassB = new() { BB = true, BI = 43 };
            Console.WriteLine($"Exiting EmbeddedClassTypeA constructor. Values are: {this})");
        }
    }

    public class EmbeddedClassTypeB
    {
        public int BI { get; set; }
        public bool BB { get; set; }
        public string BS { get; set; }

        public override string ToString() => $"{BI}|{BB}|{BS}";

        public EmbeddedClassTypeB()
        {
            Console.WriteLine($"Entering EmbeddedClassTypeB constructor. Values are: {this}");
            BI = 23;
            BB = false;
            BS = "BBBabc";
            Console.WriteLine($"Exiting EmbeddedClassTypeB constructor. Values are: {this})");
        }
    }

    public static void Main()
    {
        var a = new EmbeddedClassTypeA
        {
            I = 103,
            B = false,
            ClassB = { BI = 100003 }
        };
        Console.WriteLine($"After initializing EmbeddedClassTypeA: {a}");

        var a2 = new EmbeddedClassTypeA
        {
            I = 103,
            B = false,
            ClassB = new() { BI = 100003 } //New instance
        };
        Console.WriteLine($"After initializing EmbeddedClassTypeA a2: {a2}");
    }

    // Output:
    //Entering EmbeddedClassTypeA constructor Values are: 0|False||||
    //Entering EmbeddedClassTypeB constructor Values are: 0|False|
    //Exiting EmbeddedClassTypeB constructor Values are: 23|False|BBBabc)
    //Exiting EmbeddedClassTypeA constructor Values are: 3|True|abc|||43|True|BBBabc)
    //After initializing EmbeddedClassTypeA: 103|False|abc|||100003|True|BBBabc
    //Entering EmbeddedClassTypeA constructor Values are: 0|False||||
    //Entering EmbeddedClassTypeB constructor Values are: 0|False|
    //Exiting EmbeddedClassTypeB constructor Values are: 23|False|BBBabc)
    //Exiting EmbeddedClassTypeA constructor Values are: 3|True|abc|||43|True|BBBabc)
    //Entering EmbeddedClassTypeB constructor Values are: 0|False|
    //Exiting EmbeddedClassTypeB constructor Values are: 23|False|BBBabc)
    //After initializing EmbeddedClassTypeA a2: 103|False|abc|||100003|False|BBBabc
}

次の例は、ClassB の初期化プロセスで、元のインスタンスの他の値を保持しながら特定の値を更新する方法を示しています。 初期化子では現在のインスタンスが再利用されます。ClassB の値は、100003 (ここで割り当てる新しい値)、true (EmbeddedClassTypeA の初期化から維持)、BBBabc (変更されていない EmbeddedClassTypeB の既定値) になります。

コレクション初期化子

コレクション初期化子を使うと、IEnumerable を実装するコレクション型を初期化するときに 1 つ以上の要素の初期化子を指定でき、適切なシグネチャの Add をインスタンス メソッドまたは拡張メソッドとして使用できます。 要素の初期化子は、値、式、またはオブジェクト初期化子にできます。 コレクション初期化子を使うと、コンパイラが呼び出しを自動的に追加するので、複数の呼び出しを指定する必要はありません。

2 つの単純なコレクション初期化子を次の例に示します。

List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
List<int> digits2 = new List<int> { 0 + 1, 12 % 3, MakeInt() };

次のコレクション初期化子は、前の例で定義されている Cat クラスのオブジェクトをオブジェクト初期化子を使用して初期化します。 個々のオブジェクト初期化子は、かっこで囲まれ、コンマで区切られています。

List<Cat> cats = new List<Cat>
{
    new Cat{ Name = "Sylvester", Age=8 },
    new Cat{ Name = "Whiskers", Age=2 },
    new Cat{ Name = "Sasha", Age=14 }
};

コレクションの Add メソッドで許容されている場合、コレクション初期化子の要素として null を指定できます。

List<Cat?> moreCats = new List<Cat?>
{
    new Cat{ Name = "Furrytail", Age=5 },
    new Cat{ Name = "Peaches", Age=4 },
    null
};

コレクションがインデックスを読み取り/書き込みできる場合は、インデックス付きの要素を指定できます。

var numbers = new Dictionary<int, string>
{
    [7] = "seven",
    [9] = "nine",
    [13] = "thirteen"
};

上記のサンプルの場合、Item[TKey] を呼び出して値を設定するコードが生成されます。 次の構文を使用し、ディクショナリやその他の連想コンテナーを初期化することもできました。 インデクサー構文の代わりに、括弧と代入によって 1 つのオブジェクトと複数の値が処理されていることにご注目ください。

var moreNumbers = new Dictionary<int, string>
{
    {19, "nineteen" },
    {23, "twenty-three" },
    {42, "forty-two" }
};

この初期化子の例では、Add(TKey, TValue) を呼び出し、3 つの項目をディクショナリに追加しています。 連想コレクションを初期化するこれら 2 つの異なる方法には、コンパイラによって生成されるメソッド呼び出しにより、動作にわずかな違いがあります。 いずれの場合も Dictionary クラスが使用されます。 他の型では、パブリック API に基づいて、いずれか一方のみがサポートされる場合があります。

コレクションの読み取り専用プロパティの初期化でのオブジェクト初期化子

次の例における CatOwnerCats プロパティのように、一部のクラスには、プロパティが読み取り専用であるコレクション プロパティが含まれる場合があります。

public class CatOwner
{
    public IList<Cat> Cats { get; } = new List<Cat>();
}

プロパティに新しいリストを割り当てることはできないので、これまでに説明したコレクション初期化子構文を使うことはできません。

CatOwner owner = new CatOwner
{
    Cats = new List<Cat>
    {
        new Cat{ Name = "Sylvester", Age=8 },
        new Cat{ Name = "Whiskers", Age=2 },
        new Cat{ Name = "Sasha", Age=14 }
    }
};

ただし、次に示すように、リスト作成 (new List<Cat>) を省略することで、初期化構文を使用して、新しいエントリを Cats に追加できます。

CatOwner owner = new CatOwner
{
    Cats =
    {
        new Cat{ Name = "Sylvester", Age=8 },
        new Cat{ Name = "Whiskers", Age=2 },
        new Cat{ Name = "Sasha", Age=14 }
    }
};

追加するエントリのセットが中かっこで囲まれています。 上のコードは次のように書くのと同じです。

CatOwner owner = new ();
owner.Cats.Add(new Cat{ Name = "Sylvester", Age=8 });
owner.Cats.Add(new Cat{ Name = "Whiskers", Age=2 });
owner.Cats.Add(new Cat{ Name = "Sasha", Age=14 });

次の例では、オブジェクトの概念とコレクション初期化子の概念が組み合わさっています。

public class InitializationSample
{
    public class Cat
    {
        // Automatically implemented properties.
        public int Age { get; set; }
        public string? Name { get; set; }

        public Cat() { }

        public Cat(string name)
        {
            Name = name;
        }
    }

    public static void Main()
    {
        Cat cat = new Cat { Age = 10, Name = "Fluffy" };
        Cat sameCat = new Cat("Fluffy"){ Age = 10 };

        List<Cat> cats = new List<Cat>
        {
            new Cat { Name = "Sylvester", Age = 8 },
            new Cat { Name = "Whiskers", Age = 2 },
            new Cat { Name = "Sasha", Age = 14 }
        };

        List<Cat?> moreCats = new List<Cat?>
        {
            new Cat { Name = "Furrytail", Age = 5 },
            new Cat { Name = "Peaches", Age = 4 },
            null
        };

        // Display results.
        System.Console.WriteLine(cat.Name);

        foreach (Cat c in cats)
        {
            System.Console.WriteLine(c.Name);
        }

        foreach (Cat? c in moreCats)
        {
            if (c != null)
            {
                System.Console.WriteLine(c.Name);
            }
            else
            {
                System.Console.WriteLine("List element has null value.");
            }
        }
    }
    // Output:
    //Fluffy
    //Sylvester
    //Whiskers
    //Sasha
    //Furrytail
    //Peaches
    //List element has null value.
}

次に示すのは、IEnumerable を実装し、複数のパラメーターを持つ Add メソッドを含むオブジェクトの例です。 Add メソッドのシグネチャに対応するリスト内の項目ごとに複数の要素を持つコレクション初期化子を使っています。

public class FullExample
{
    class FormattedAddresses : IEnumerable<string>
    {
        private List<string> internalList = new List<string>();
        public IEnumerator<string> GetEnumerator() => internalList.GetEnumerator();

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => internalList.GetEnumerator();

        public void Add(string firstname, string lastname,
            string street, string city,
            string state, string zipcode) => internalList.Add($"""
            {firstname} {lastname}
            {street}
            {city}, {state} {zipcode}
            """
            );
    }

    public static void Main()
    {
        FormattedAddresses addresses = new FormattedAddresses()
        {
            {"John", "Doe", "123 Street", "Topeka", "KS", "00000" },
            {"Jane", "Smith", "456 Street", "Topeka", "KS", "00000" }
        };

        Console.WriteLine("Address Entries:");

        foreach (string addressEntry in addresses)
        {
            Console.WriteLine("\r\n" + addressEntry);
        }
    }

    /*
        * Prints:

        Address Entries:

        John Doe
        123 Street
        Topeka, KS 00000

        Jane Smith
        456 Street
        Topeka, KS 00000
        */
}

Add メソッドでは、次の例で示すように、params キーワードを使用して可変数個の引数を受け取ることができます。 この例では、インデクサーのカスタム実装と、インデクサーを使用したコレクションの初期化を示しています。 C# 13 以降の params パラメーターは、配列に制限されません。 コレクション型またはインターフェイスを指定できます。

public class DictionaryExample
{
    class RudimentaryMultiValuedDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, List<TValue>>> where TKey : notnull
    {
        private Dictionary<TKey, List<TValue>> internalDictionary = new Dictionary<TKey, List<TValue>>();

        public IEnumerator<KeyValuePair<TKey, List<TValue>>> GetEnumerator() => internalDictionary.GetEnumerator();

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => internalDictionary.GetEnumerator();

        public List<TValue> this[TKey key]
        {
            get => internalDictionary[key];
            set => Add(key, value);
        }

        public void Add(TKey key, params TValue[] values) => Add(key, (IEnumerable<TValue>)values);

        public void Add(TKey key, IEnumerable<TValue> values)
        {
            if (!internalDictionary.TryGetValue(key, out List<TValue>? storedValues))
            {
                internalDictionary.Add(key, storedValues = new List<TValue>());
            }
            storedValues.AddRange(values);
        }
    }

    public static void Main()
    {
        RudimentaryMultiValuedDictionary<string, string> rudimentaryMultiValuedDictionary1
            = new RudimentaryMultiValuedDictionary<string, string>()
            {
                {"Group1", "Bob", "John", "Mary" },
                {"Group2", "Eric", "Emily", "Debbie", "Jesse" }
            };
        RudimentaryMultiValuedDictionary<string, string> rudimentaryMultiValuedDictionary2
            = new RudimentaryMultiValuedDictionary<string, string>()
            {
                ["Group1"] = new List<string>() { "Bob", "John", "Mary" },
                ["Group2"] = new List<string>() { "Eric", "Emily", "Debbie", "Jesse" }
            };
        RudimentaryMultiValuedDictionary<string, string> rudimentaryMultiValuedDictionary3
            = new RudimentaryMultiValuedDictionary<string, string>()
            {
                {"Group1", new string []{ "Bob", "John", "Mary" } },
                { "Group2", new string[]{ "Eric", "Emily", "Debbie", "Jesse" } }
            };

        Console.WriteLine("Using first multi-valued dictionary created with a collection initializer:");

        foreach (KeyValuePair<string, List<string>> group in rudimentaryMultiValuedDictionary1)
        {
            Console.WriteLine($"\r\nMembers of group {group.Key}: ");

            foreach (string member in group.Value)
            {
                Console.WriteLine(member);
            }
        }

        Console.WriteLine("\r\nUsing second multi-valued dictionary created with a collection initializer using indexing:");

        foreach (KeyValuePair<string, List<string>> group in rudimentaryMultiValuedDictionary2)
        {
            Console.WriteLine($"\r\nMembers of group {group.Key}: ");

            foreach (string member in group.Value)
            {
                Console.WriteLine(member);
            }
        }
        Console.WriteLine("\r\nUsing third multi-valued dictionary created with a collection initializer using indexing:");

        foreach (KeyValuePair<string, List<string>> group in rudimentaryMultiValuedDictionary3)
        {
            Console.WriteLine($"\r\nMembers of group {group.Key}: ");

            foreach (string member in group.Value)
            {
                Console.WriteLine(member);
            }
        }
    }

    /*
        * Prints:

        Using first multi-valued dictionary created with a collection initializer:

        Members of group Group1:
        Bob
        John
        Mary

        Members of group Group2:
        Eric
        Emily
        Debbie
        Jesse

        Using second multi-valued dictionary created with a collection initializer using indexing:

        Members of group Group1:
        Bob
        John
        Mary

        Members of group Group2:
        Eric
        Emily
        Debbie
        Jesse

        Using third multi-valued dictionary created with a collection initializer using indexing:

        Members of group Group1:
        Bob
        John
        Mary

        Members of group Group2:
        Eric
        Emily
        Debbie
        Jesse
        */
}

関連項目