Aracılığıyla paylaş


Nesne ve koleksiyon başlatıcıları (C# programlama kılavuzu)

C# bir nesne veya koleksiyon örneği oluşturmanızı ve tek bir deyimde üye atamaları gerçekleştirmenizi sağlar.

Nesne başlatıcılar

Nesne başlatıcılar, bir nesneyi oluşturduğunuzda erişilebilir alanlara veya özelliklere değer atamanıza olanak sağlar. Oluşturucuyu çağırmanız ve ardından atama deyimlerini kullanmanız gerekmez. Nesne başlatıcı söz dizimi, bir oluşturucu için bağımsız değişkenler belirtmenize veya bağımsız değişkenleri ve parantezleri atlamanıza olanak tanır. Nesne başlatıcılarını tutarlı bir şekilde kullanma yönergeleri için bkz. Nesne başlatıcıları kullanma (stil kuralı IDE0017). Aşağıdaki örnekte, Catadlandırılmış türe sahip bir nesne başlatıcının nasıl kullanılacağı ve parametresiz oluşturucunun nasıl çağrıldığı gösterilmektedir. Cat sınıfında otomatik olarak uygulanan özelliklerin kullanımına dikkat edin. Daha fazla bilgi için bkz . Otomatik olarak uygulanan özellikler.

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

Nesne başlatıcı söz dizimi, bir örnek oluşturmanıza ve atanan özellikleriyle yeni oluşturulan nesneyi atamadaki değişkene atamanıza olanak tanır.

İç içe nesne özelliklerinden başlayarak, anahtar sözcük olmadan nesne başlatıcı söz dizimini new kullanabilirsiniz. Bu söz dizimi, Property = { ... } var olan iç içe nesnelerin üyelerini başlatmanıza olanak tanır, bu da salt okunur özellikler için kullanışlıdır. Daha fazla bilgi için bkz. Sınıf türündeki özelliklere sahip Nesne Başlatıcılar.

Nesne başlatıcılar, alanları ve özellikleri atamanın yanı sıra dizin oluşturucuları da ayarlayabilir. Şu temel Matrix sınıfı göz önünde bulundurun:

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

Aşağıdaki kodu kullanarak kimlik matrisini başlatabilirsiniz:

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

Erişilebilir bir ayarlayıcı içeren herhangi bir erişilebilir dizin oluşturucu, bağımsız değişken sayısından veya türlerinden bağımsız olarak nesne başlatıcıdaki ifadelerden biri olarak kullanılabilir. Dizin bağımsız değişkenleri atamanın sol tarafını oluşturur ve değer ifadenin sağ tarafıdır. Örneğin, aşağıdaki başlatıcıların tümü uygun dizin oluşturuculara sahipse IndexersExample geçerlidir:

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

Yukarıdaki kodun derlenmesi için türün IndexersExample aşağıdaki üyelere sahip olması gerekir:

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

Anonim türleri olan nesne başlatıcılar

Nesne başlatıcıları herhangi bir bağlamda kullanabilirsiniz ancak bunlar özellikle Language-Integrated Sorgu (LINQ) ifadelerinde kullanışlıdır. Sorgu ifadeleri genellikle aşağıdaki bildirimde gösterildiği gibi yalnızca bir nesne başlatıcı kullanarak başlatabileceğiniz anonim türleri kullanır.

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

Anonim türler kullanılarak, select LINQ sorgu ifadesindeki yan tümcesi özgün dizinin nesnelerini, değeri ve şekli özgünden farklı nesnelere dönüştürebilir. Her nesnedeki bilgilerin yalnızca bir bölümünü bir dizide depolamak isteyebilirsiniz. Aşağıdaki örnekte, bir ürün nesnesinin (p) birçok alan ve yöntem içerdiğini ve yalnızca ürün adını ve birim fiyatını içeren bir nesne dizisi oluşturmakla ilgilendiğinizi varsayalım.

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

Bu sorguyu yürüttüğünüzde productInfos değişkeni, bu örnekte gösterildiği gibi foreach deyiminde erişebileceğiniz bir nesne dizisi içerir.

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

Yeni anonim türdeki her nesne, özgün nesnedeki özellikler veya alanlarla aynı adları alan iki ortak özelliğe sahiptir. Anonim bir tür oluşturduğunuzda bir alanı yeniden adlandırabilirsiniz. Aşağıdaki örnek UnitPrice alanını Price olarak yeniden adlandırır.

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

required modifikatörü ile nesne başlatıcıları

required Çağıranları nesne başlatıcı kullanarak bir özelliğin veya alanın değerini ayarlamaya zorlamak için anahtar sözcüğünü kullanın. Gerekli özellikleri oluşturucu parametreleri olarak ayarlamanız gerekmez. Derleyici, tüm çağıranların bu değerleri başlatmasını sağlar.

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();

Özellikle yönetecek birden çok alanınız veya özelliğiniz varsa ve bunların tümünü oluşturucuya eklemek istemediğinizde, nesnenizin düzgün bir şekilde başlatıldığını garanti etmek tipik bir uygulamadır.

init erişimci ile nesne başlatıcıları

Erişimci init kullanarak, nesnenin başlatma işleminden sonra değişmediğinden emin olabilirsiniz. Özellik değerinin ayarını kısıtlamaya yardımcı olur.

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

Gereken yalnızca başlatma özellikleri, değişmez yapıları desteklerken, bu türün kullanıcılarına doğal sözdizimi sağlar.

Sınıf türündeki özelliklere sahip nesne başlatıcılar

Nesneleri sınıf türündeki özelliklerle başlatırken iki farklı söz dizimi kullanabilirsiniz:

  1. Anahtar sözcüğü olmayan new nesne başlatıcı: Property = { ... }
  2. Anahtar sözcük ile nesne başlatıcı:

Bu söz dizimleri farklı davranır. Aşağıdaki örnekte her iki yaklaşım da gösterilmektedir:

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
}

Önemli farklar

  • Anahtar sözcük olmadan new (ClassB = { BI = 100003 }): Bu söz dizimi, nesne oluşturucusunun oluşturduğu özelliğin mevcut örneğini değiştirir. Mevcut nesnede üye başlatıcılarını çağırır.

  • anahtar sözcüğüyle new (ClassB = new() { BI = 100003 }): Bu söz dizimi yeni bir örnek oluşturur ve var olan herhangi bir örneği değiştirerek özelliğe atar.

Başlatıcı, new olmadan geçerli örneği yeniden kullanır. Önceki örnekte ClassB'nin değerleri şunlardır: 100003 (yeni değer atandı), true (EmbeddedClassTypeA'nın başlatılmasından korunur), BBBabc (EmbeddedClassTypeB'den varsayılan olarak değiştirilmez).

Salt okunur özellikler için new olmadan nesne başlatıcılar

new olmayan söz dizimi, salt okunur özelliklerle kullanışlıdır. Yeni bir örnek atayamazsınız, ancak mevcut örneğin üyelerini yine de başlatabilirsiniz:

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

Bu yaklaşım, içeren özelliğin ayarlayıcısı olmadığında bile iç içe nesneleri başlatmanıza olanak tanır.

Koleksiyon başlatıcıları

Koleksiyon başlatıcıları, uygun imzaya sahip bir Add yöntemi bir örnek yöntemi veya uzantı yöntemi olarak içeren ve IEnumerable arayüzünü uygulayan bir koleksiyon türünü başlatırken bir veya daha fazla öğe başlatıcı belirtmenize olanak tanır. Öğe başlatıcıları bir değer, ifade veya nesne başlatıcı olabilir. Koleksiyon başlatıcı kullanarak birden çok çağrı belirtmeniz gerekmez; derleyicisi çağrıları otomatik olarak ekler. Koleksiyon başlatıcılarını tutarlı bir şekilde kullanma yönergeleri için bkz. Koleksiyon başlatıcılarını kullanma (stil kuralı IDE0028). Koleksiyon başlatıcıları LINQ sorgularında da yararlıdır.

Aşağıdaki örnekte iki basit koleksiyon başlatıcı gösterilmektedir:

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

Aşağıdaki koleksiyon başlatıcı, önceki örnekte tanımlanan sınıfın Cat nesnelerini başlatmak için nesne başlatıcıları kullanır. Bireysel nesne başlatıcıları küme ayraçlarıyla çevrelenir ve virgülle ayrılır.

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

Koleksiyonun yöntemi buna izin veriyorsa, bir koleksiyon başlatıcısında null'u bir öğe olarak belirtebilirsiniz.

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

Diğer liste veya listeleri kopyalayan bir liste oluşturmak için bir spread öğesi kullanabilirsiniz.

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

Ayrıca, bir yayılma öğesi kullanarak ek öğeler de ekleyin.

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

Koleksiyon okuma/yazma dizin oluşturmayı destekliyorsa dizine alınan öğeleri belirtebilirsiniz.

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

Yukarıdaki örnek, değerleri ayarlamak için Item[TKey] fonksiyonunu çağıran kod oluşturur. Aşağıdaki söz dizimini kullanarak sözlükleri ve diğer ilişkilendirilebilir kapsayıcıları da başlatabilirsiniz. Parantez ve atama ile indeksleyici söz dizimi yerine, birden çok değer içeren bir nesne kullanır.

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

Bu başlatıcı örneği, üç öğeyi sözlüğe eklemek için çağırır Add(TKey, TValue) . İlişkili koleksiyonları başlatmanın bu iki farklı yolu, derleyicinin oluşturduğu yöntem çağrıları nedeniyle biraz farklı davranışlara sahiptir. Her iki varyant da Dictionary sınıfıyla çalışır. Diğer türler, genel API'lerine bağlı olarak yalnızca birini veya diğerini destekleyebilir.

Koleksiyon ifadesi bağımsız değişkenleri

C# 15'ten itibaren, bağımsız değişkenleri koleksiyonun oluşturucusuna geçirmek için bir koleksiyon ifadesi içerisinde ilk öğe olarak with(...) öğesini kullanın. Bu özelliği kullanarak kapasite, karşılaştırıcılar veya diğer oluşturucu parametrelerini doğrudan koleksiyon ifadesi söz diziminde belirtebilirsiniz:

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

Desteklenen hedef türleri ve kısıtlamalar da dahil olmak üzere koleksiyon ifadesi bağımsız değişkenleri hakkında daha fazla bilgi için bkz . Koleksiyon ifadesi bağımsız değişkenleri.

Koleksiyonların salt okunur özelliklerini başlatma ile nesne başlatıcıları

Bazı sınıflar, aşağıdaki örnekte CatOwner özelliği gibi, Cats özelliğinin salt okunur olduğu koleksiyon özelliklerine sahiptir:

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

Özelliğe yeni bir liste atanamayacağından, daha önce açıklanan koleksiyon başlatıcı söz dizimini kullanamazsınız.

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

Ancak, başlatma söz dizimini kullanarak ve liste oluşturmayı atlayarak Cats'e yeni girdiler ekleyebilirsiniz, aşağıdaki örnekte gösterildiği gibi:

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

Eklenecek girdi kümesi küme ayraçlarıyla çevrili olarak görünür. Yukarıdaki kod yazma işlemiyle aynıdır:

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

Örnekler

Aşağıdaki örnek, nesne ve koleksiyon başlatıcı kavramlarını birleştirir.

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

Aşağıdaki örnek, IEnumerable uygulayan ve birden çok parametreye sahip bir Add yöntemi içeren bir nesne göstermektedir. Listede, Add yönteminin imzasına uyan her bir öğe için birden çok eleman içeren bir koleksiyon başlatıcısı kullanır.

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 yöntemleri, aşağıdaki örnekte gösterildiği gibi değişken sayıda bağımsız değişken almak için params anahtar sözcüğünü kullanabilir. Bu örnek, dizinleri kullanarak bir koleksiyonu başlatmak için dizin oluşturucunun özel uygulamasını da gösterir. C# 13'den başlayarak, params parametre bir diziyle sınırlı değildir. Bir koleksiyon türü veya arabirimi olabilir.

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
        */
}