Initialiseurs d’objet et de collection (Guide de programmation C#)

C# vous permet d’instancier un objet ou une collection et d’effectuer des affectations de membres dans une seule instruction.

Initialiseurs d’objet

Les initialiseurs d'objet vous permettent d'affecter des valeurs aux champs ou propriétés accessibles d'un objet, au moment de sa création, sans devoir appeler un constructeur suivi de ses lignes d'instructions d'assignation. La syntaxe de l'initialiseur de l'objet vous permet de spécifier les arguments d'un constructeur ou de les omettre (et la syntaxe de parenthèses). L'exemple suivant montre comment utiliser l'initialiseur de l'objet de type nommé, Cat, et comment appeler le constructeur sans paramètre. Notez l'utilisation de propriétés implémentées automatiquement dans la classe Cat. Pour plus d’informations, consultez Propriétés implémentées automatiquement.

public class Cat
{
    // Auto-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 };

La syntaxe des initialiseurs d’objet vous permet de créer une instance qui assigne l’objet nouvellement créé, avec ses propriétés, à la variable dans l’assignation.

À compter de C# 6, les initialiseurs d’objet peuvent, en plus d’affecter des champs et des propriétés, définir des indexeurs. Prenez l’exemple de cette classe Matrix de base :

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

Vous pouvez initialiser la matrice identity avec le code suivant :

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

Tout indexeur accessible qui contient un setter accessible peut être utilisé comme l’une des expressions d’un initialiseur d’objet, indépendamment du nombre ou des types d’arguments. Les arguments d’index forment le côté gauche de l’affectation, et la valeur le côté droit de l’expression. Par exemple, les éléments suivants sont tous valides si IndexersExample a les indexeurs appropriés :

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

Pour le code précédent à compiler, le type IndexersExample doit avoir les membres suivants :

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

Initialiseurs d'objet avec des types anonymes

Bien que les initialiseurs d’objets puissent être utilisés dans n’importe quel contexte, ils sont particulièrement utiles dans les expressions de requête LINQ. Les expressions de requête utilisent souvent des types anonymes, qui peuvent uniquement être initialisés à l’aide d’un initialiseur d’objet, comme indiqué dans la déclaration suivante.

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

Les types anonymes permettent select à la clause d’une expression de requête LINQ de transformer les objets de la séquence d’origine en objets dont la valeur et la forme peuvent différer de l’original. Cela s'avère utile si vous souhaitez stocker uniquement une partie des informations de chaque objet d'une séquence. Dans l'exemple suivant, supposons qu'un objet de produit (p) contient de nombreux champs et méthodes et que vous souhaitez uniquement créer une séquence d'objets qui contiennent le nom de produit et le prix unitaire.

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

Lorsque cette requête est exécutée, la variable productInfos contient une séquence d'objets qui sont accessibles dans une instruction foreach comme indiqué dans cet exemple :

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

Chaque objet dans le nouveau type anonyme a deux propriétés publiques qui reçoivent les mêmes noms que les propriétés ou champs de l’objet d’origine. Vous pouvez également renommer un champ lorsque vous créez un type anonyme. L'exemple suivant renomme le champ UnitPrice en Price.

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

Initialiseurs de collection

Les initialiseurs de collection vous permettent de spécifier un ou plusieurs initialiseurs d’élément quand vous initialisez un type de collection qui implémente IEnumerable et qui utilise Add comme méthode d’instance ou méthode d’extension avec la signature appropriée. Les initialiseurs d’élément peuvent être une valeur simple, une expression ou un initialiseur d’objet. En utilisant un initialiseur de collection, il n’est pas nécessaire de spécifier plusieurs appels ; le compilateur les ajoute automatiquement.

L’exemple suivant montre deux initialiseurs de collection simples :

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

L'initialiseur de collection suivant utilise des initialiseurs d'objets pour initialiser les objets de la classe Cat définis dans un exemple précédent. Notez que les initialiseurs d'objets individuels sont placés entre accolades et séparés par une virgule.

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

Vous pouvez spécifier Null comme élément dans un initialiseur de collection si la méthode Add de la collection l’autorise.

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

Vous pouvez spécifier des éléments indexés si la collection prend en charge l’indexation en lecture/écriture.

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

L’exemple précédent génère du code qui appelle Item[TKey] pour définir les valeurs. Avant C# 6, vous pouviez initialiser des dictionnaires et d’autres conteneurs associatifs à l’aide de la syntaxe suivante. Notez que la syntaxe de l’indexeur, avec des parenthèses et une affectation, est remplacée par un objet avec plusieurs valeurs :

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

Cet exemple d’initialiseur appelle Add(TKey, TValue) pour ajouter les trois éléments dans le dictionnaire. Ces deux manières d’initialiser les collections associatives ont un comportement légèrement différent en raison des appels de méthode générés par le compilateur. Les deux variantes fonctionnent avec la classe Dictionary. D’autres types peuvent uniquement prendre en charge l’une ou l’autre en fonction de leur API publique.

Initialiseurs d’objets avec l’initialisation de propriété en lecture seule de la collection

Certaines classes peuvent avoir des propriétés de collection où la propriété est en lecture seule, comme la Cats propriété de CatOwner dans le cas suivant :

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

Vous ne pourrez pas utiliser la syntaxe de l’initialiseur de collection décrite jusqu’à présent, car une nouvelle liste ne peut pas être affectée à la propriété :

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

Toutefois, de nouvelles entrées peuvent être ajoutées à Cats néanmoins à l’aide de la syntaxe d’initialisation en omettant la création de la liste ( new List<Cat> ), comme indiqué ci-dessous :

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

L’ensemble d’entrées à ajouter est simplement entouré par des accolades. Les éléments ci-dessus sont identiques à l’écriture :

CatOwner owner = new CatOwner();
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 });

Exemples

L’exemple suivant combine les concepts d’initialiseurs d’objet et de collection.

public class InitializationSample
{
    public class Cat
    {
        // Auto-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.
}

L’exemple suivant montre un objet qui implémente IEnumerable et qui contient une méthode Add avec plusieurs paramètres. Il utilise un initialiseur de collection avec plusieurs éléments dans la liste correspondant à la signature de la méthode 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
         */
    }

Les méthodes Add peuvent utiliser le mot clé params pour sélectionner un nombre variable d’arguments, comme indiqué dans l’exemple suivant. Cet exemple montre également l’implémentation personnalisée d’un indexeur pour initialiser une collection à l’aide d’index.

public class DictionaryExample
{
    class RudimentaryMultiValuedDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, List<TValue>>>
    {
        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
     */
}

Voir aussi