共用方式為


物件與集合初始化器(C# 程式設計指南)

C# 讓你能在單一語句中實例化物件或集合並執行成員指派。

物件初始設定式

物件初始化器讓你在建立物件時,可以為任何可存取的欄位或屬性指派值。 你不需要呼叫建構子然後再使用賦值語句。 物件初始化器的語法允許你指定建構子的參數,或省略參數和括號。 關於如何一致使用物件初始化器的指引,請參見 「使用物件初始化器(風格規則 IDE0017)」。 以下範例展示了如何使用具有命名型別的物件初始化器, 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 };

物件初始化器的語法允許你建立一個實例,並將新建立的物件及其分配屬性指派到指派中的變數上。

從巢狀物件屬性開始,您可以使用不含 關鍵詞的物件初始化表達式語法 new 。 這個語法 Property = { ... },使你能初始化現有巢狀物件的成員,這對於唯讀特性非常有用。 如需詳細資訊,請參閱 具有類別類型屬性的物件初始化運算式

物件初始設定式除了指派欄位和屬性之外,還可以設定索引子。 請考慮此基本 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,
};

任何包含可存取 setter 的可存取索引子都可作為物件初始設定式中的其中一個運算式,不論引數的數目或類型為何。 指派左側是由索引引數組成,運算式右側是其值。 例如,如果 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 { ... }; }

具有匿名型態的物件初始化器

雖然你可以在任何情境中使用物件初始化器,但它們在 Language-Integrated 查詢(LINQ)表達式中特別有用。 查詢表達式經常使用匿名 型別,只有使用物件初始化器才能初始化,如下方宣告所示。

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

透過使用匿名型別, select LINQ 查詢表達式中的子句可以將原始序列的物件轉換成其值與形狀與原始不同的物件。 您可能只想要儲存序列中每個物件的部分資訊。 在下列範例中,假設產品物件 (p) 包含許多欄位和方法,而您只想要建立包含產品名稱和單價的物件序列。

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

當你執行此查詢時,變 productInfos 數包含一串你可以在語句中 foreach 存取的物件序列,如本範例所示:

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

新的匿名型別中的每個物件都有兩個公用屬性,這兩個屬性會接收與原始物件中的屬性或欄位相同的名稱。 你也可以在建立匿名型態時重新命名欄位。 以下範例將欄位 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 屬性支援不可變的結構,同時允許型別使用者採用自然的語法。

具有類別型態屬性的物件初始化器

當你初始化具有類別型別屬性的物件時,可以使用兩種不同的語法:

  1. 不含 new 關鍵字的物件初始化表示式Property = { ... }
  2. 具有 new 關鍵字的物件初始化運算式Property = new() { ... }

這些語法的行為方式不同。 下列範例示範這兩種方法:

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
}

主要差異

  • 若無 new 關鍵字ClassB = { BI = 100003 }): ,此語法會修改物件建構器所建立的現有屬性實例。 它會呼叫現有物件上的成員初始化表達式。

  • 使用 new 關鍵字ClassB = new() { BI = 100003 }):此語法會建立一個新實例並將其指派給屬性,取代任何現有實例。

初始化器在沒有 new 的情況下重複使用目前的實例。 在上一個範例中,ClassB 的值為: 100003 (指派的新值)、 true (保留 EmbeddedClassTypeA 的初始化)、 BBBabc (EmbeddedClassTypeB 的預設值不變)。

沒有 new 唯讀屬性的物件初始化表達式

沒有 new 的語法對於唯讀屬性很有用。 你不能指派新實例,但仍可初始化現有實例的成員:

public class ReadOnlyPropertyExample
{
    public class Settings
    {
        public string Theme { get; set; } = "Light";
        public int FontSize { get; set; } = 12;
    }

    public class Application
    {
        public string Name { get; set; } = "";
        // This property is read-only - it can only be set during construction
        public Settings AppSettings { get; } = new();
    }

    public static void Example()
    {
        // You can still initialize the nested object's properties
        // even though AppSettings property has no setter
        var app = new Application
        {
            Name = "MyApp",
            AppSettings = { Theme = "Dark", FontSize = 14 }
        };

        // This would cause a compile error because AppSettings has no setter:
        // app.AppSettings = new Settings { Theme = "Dark", FontSize = 14 };

        Console.WriteLine($"App: {app.Name}, Theme: {app.AppSettings.Theme}, Font Size: {app.AppSettings.FontSize}");
    }
}

這個方法可讓您初始化巢狀物件,即使包含的屬性沒有 setter 也可以。

集合初始設定式

集合初始化器允許你在初始化一個實作 IEnumerable 且擁有 Add 適當簽名的方法作為實例方法或擴充方法的集合類型時,指定一個或多個元素初始化器。 項目初始設定式可以是值、運算式或物件初始設定式。 藉由使用集合初始設定式,您就不需要指定多個呼叫,編譯器會自動新增呼叫。 關於如何一致使用集合初始化器的指引,請參見 「使用集合初始化器(樣式規則 IDE0028)」。 集合初始化器在 LINQ 查詢中也很有用。

下列範例將示範兩個簡單的集合初始設定式:

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 Cat { Name = "Sylvester", Age = 8 },
    new Cat { Name = "Whiskers", Age = 2 },
    new Cat { Name = "Sasha", Age = 14 }
];

如果集合的 null 方法允許,您可以將 Add 作為集合初始設定值中的元素指定。

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

您可以使用展開元素來建立一個複製其他清單或多個清單的清單。

List<Cat> allCats = [.. cats, .. moreCats];

並且加入額外元素,並使用擴散元素。

List<Cat> additionalCats = [.. cats, new Cat { Name = "Furrytail", Age = 5 }, .. moreCats];

如果集合支援讀取/寫入索引,您可以指定索引的項目。

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

上述範例會產生呼叫 Item[TKey] 以設定值的程式碼。 你也可以使用以下語法初始化字典和其他關聯容器。 它不使用索引器語法,並加上括號和指派,而是使用一個包含多個值的物件:

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

初始設定式範例會呼叫 Add(TKey, TValue) 來將三個項目新增至字典。 由於編譯器所產生的方法呼叫,這兩種初始化關聯集合的方式會有稍微不同的行為。 這兩種方式都可以搭配 Dictionary 類別運作。 其他類型視其公用 API 而定,可能只支援其中一種。

集合表達式參數

從 C# 15 開始,將元素 with(...) 作為 集合表達式 的第一個元素,將參數傳遞給集合的建構子。 透過使用此功能,您可以直接在集合表達式語法中指定容量、比較器或其他建構參數:

public static void CollectionExpressionWithArgumentsExample()
{
    string[] values = ["one", "two", "three"];

    // Use with() to pass capacity to the List<T> constructor
    List<string> names = [with(capacity: values.Length * 2), .. values];

    Console.WriteLine($"Created List<string> with capacity: {names.Capacity}");
    Console.WriteLine($"List contains {names.Count} elements: [{string.Join(", ", names)}]");

    // Use with() to pass a comparer to the HashSet<T> constructor
    HashSet<string> caseInsensitiveSet = [with(StringComparer.OrdinalIgnoreCase), "Hello", "HELLO"];
    // caseInsensitiveSet contains only one element because "Hello" and "HELLO" are equal

    Console.WriteLine($"HashSet with case-insensitive comparer contains {caseInsensitiveSet.Count} elements: [{string.Join(", ", caseInsensitiveSet)}]");
    Console.WriteLine("Note: 'Hello' and 'HELLO' are treated as the same element due to OrdinalIgnoreCase comparer");
}

欲了解更多關於集合表達式參數的資訊,包括支援的目標類型與限制,請參見集合表達式參數。

具有集合唯讀屬性初始化的物件初始化器

有些類別具有集合屬性,該屬性為唯讀,例如在以下情況下的 Cats 屬性 CatOwner

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

        List<Cat> allCats = [.. cats, new Cat { Name = "Łapka", Age = 5 }, cat, .. moreCats];

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

下列範例顯示一個物件,該物件實作 IEnumerable 並包含一個具有多個參數的 Add 方法。 其會使用集合初始設定式,這些初始設定式具有多個與 Add 方法簽章對應的元素,每個元素屬於清單中的項目。

public class FullExample
{
    class FormattedAddresses : IEnumerable<string>
    {
        private List<string> internalList = new();
        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());
            }
            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
        */
}