Collections (C#)

Pour de nombreuses applications, vous voulez créer et gérer des groupes d’objets connexes. Il existe deux manières de grouper des objets : en créant des tableaux d’objets ou des collections d’objets.

Les tableaux s’avèrent particulièrement utiles pour créer et utiliser un nombre fixe d’objets fortement typés. Pour plus d’informations sur les tableaux, consultez Tableaux.

Les collections offrent plus de souplesse quand il s’agit d’utiliser des groupes d’objets. Contrairement aux tableaux, le groupe d’objets que vous utilisez peut être développé et réduit de manière dynamique selon les modifications de l’application. Pour certaines collections, vous pouvez assigner une clé à un objet que vous placez dans la collection pour vous permettre de récupérer rapidement l’objet à l’aide de la clé.

Une collection est une classe, vous devez déclarer une instance de la classe avant de pouvoir ajouter des éléments à la collection.

Si votre collection contient des éléments d’un seul type de données, vous pouvez utiliser une des classes dans l’espace de noms System.Collections.Generic. Une collection générique applique la cohérence des types pour éviter qu’un autre type puisse y être ajouté. Quand vous récupérez un élément d’une collection générique, il n’est pas utile de déterminer son type de données ou de le convertir.

Notes

Pour les exemples de cette rubrique, ajoutez des instructions using pour les espaces de noms System.Collections.Generic et System.Linq.

Dans cette rubrique

Utilisation d’une collection simple

Les exemples de cette section utilisent la classe List<T> générique, qui vous permet d’utiliser une liste d’objets fortement typée.

L’exemple suivant crée une liste de chaînes, puis itère au sein des chaînes à l’aide d’une instruction foreach.

// Create a list of strings.
var salmons = new List<string>();
salmons.Add("chinook");
salmons.Add("coho");
salmons.Add("pink");
salmons.Add("sockeye");

// Iterate through the list.
foreach (var salmon in salmons)
{
    Console.Write(salmon + " ");
}
// Output: chinook coho pink sockeye

Si le contenu d’une collection est connu d’avance, vous pouvez utiliser un initialiseur de collection pour initialiser la collection. Pour plus d’informations, consultez la page Initialiseurs d’objets et de collections.

L’exemple suivant est identique à l’exemple précédent, à la différence qu’un initialiseur de collection est utilisé pour ajouter des éléments à la collection.

// Create a list of strings by using a
// collection initializer.
var salmons = new List<string> { "chinook", "coho", "pink", "sockeye" };

// Iterate through the list.
foreach (var salmon in salmons)
{
    Console.Write(salmon + " ");
}
// Output: chinook coho pink sockeye

Vous pouvez utiliser une instruction for au lieu d’une instruction foreach pour itérer au sein d’une collection. Pour cela, accédez aux éléments de la collection à la position d’index. L’index des éléments commence à 0 et se termine au nombre d’éléments moins 1.

L’exemple suivant itère au sein des éléments d’une collection à l’aide de for au lieu de foreach.

// Create a list of strings by using a
// collection initializer.
var salmons = new List<string> { "chinook", "coho", "pink", "sockeye" };

for (var index = 0; index < salmons.Count; index++)
{
    Console.Write(salmons[index] + " ");
}
// Output: chinook coho pink sockeye

L’exemple suivant supprime un élément de la collection en spécifiant l’objet à supprimer.

// Create a list of strings by using a
// collection initializer.
var salmons = new List<string> { "chinook", "coho", "pink", "sockeye" };

// Remove an element from the list by specifying
// the object.
salmons.Remove("coho");

// Iterate through the list.
foreach (var salmon in salmons)
{
    Console.Write(salmon + " ");
}
// Output: chinook pink sockeye

L’exemple suivant supprime les éléments d’une liste générique. Au lieu d’une foreach instruction, une for instruction qui itère dans l’ordre décroissant est utilisée. En effet, avec la méthode RemoveAt, les éléments après l’élément supprimé ont une valeur d’index moins élevée.

var numbers = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// Remove odd numbers.
for (var index = numbers.Count - 1; index >= 0; index--)
{
    if (numbers[index] % 2 == 1)
    {
        // Remove the element by specifying
        // the zero-based index in the list.
        numbers.RemoveAt(index);
    }
}

// Iterate through the list.
// A lambda expression is placed in the ForEach method
// of the List(T) object.
numbers.ForEach(
    number => Console.Write(number + " "));
// Output: 0 2 4 6 8

Pour le type d’éléments de List<T>, vous pouvez également définir votre propre classe. Dans l’exemple suivant, la classe Galaxy qui est utilisée par List<T> est définie dans le code.

private static void IterateThroughList()
{
    var theGalaxies = new List<Galaxy>
        {
            new Galaxy() { Name="Tadpole", MegaLightYears=400},
            new Galaxy() { Name="Pinwheel", MegaLightYears=25},
            new Galaxy() { Name="Milky Way", MegaLightYears=0},
            new Galaxy() { Name="Andromeda", MegaLightYears=3}
        };

    foreach (Galaxy theGalaxy in theGalaxies)
    {
        Console.WriteLine(theGalaxy.Name + "  " + theGalaxy.MegaLightYears);
    }

    // Output:
    //  Tadpole  400
    //  Pinwheel  25
    //  Milky Way  0
    //  Andromeda  3
}

public class Galaxy
{
    public string Name { get; set; }
    public int MegaLightYears { get; set; }
}

Types de collections

De nombreuses collections courantes sont fournies par .NET. Chaque type de collection est conçu dans un but spécifique.

Certaines des classes de collection courantes sont décrites dans cette section :

Classes System.Collections.Generic

Vous pouvez créer une collection générique en utilisant l’une des classes dans l’espace de noms System.Collections.Generic. Une collection générique est utile quand chaque élément de la collection a le même type de données. Une collection générique applique un typage fort en autorisant uniquement l’ajout des types de données souhaités.

Le tableau suivant liste quelques classes de l’espace de noms System.Collections.Generic fréquemment utilisées :

Classe Description
Dictionary<TKey,TValue> Représente une collection de paires clé/valeur organisées en fonction de la clé.
List<T> Représente une liste d’objets accessibles par index. Fournit des méthodes de recherche, de tri et de modification de listes.
Queue<T> Représente une collection d’objets premier entré, premier sorti (FIFO).
SortedList<TKey,TValue> Représente une collection de paires clé/valeur triées par clé en fonction de l'implémentation IComparer<T> associée.
Stack<T> Représente une collection d’objets dernier entré, premier sorti (LIFO).

Pour plus d’informations, consultez Types de collections couramment utilisés, Sélection d’une classe de collection et System.Collections.Generic.

Classes System.Collections.Concurrent

Dans .NET Framework 4 et versions ultérieures, les collections de l' System.Collections.Concurrent espace de noms fournissent des opérations thread-safe efficaces pour accéder aux éléments de collection à partir de plusieurs threads.

Les classes de l’espace de noms System.Collections.Concurrent doivent être utilisées à la place des types correspondants dans les espaces de noms System.Collections.Generic et System.Collections chaque fois que plusieurs threads accèdent simultanément à la collection. Pour plus d’informations, consultez Collections thread-safe et System.Collections.Concurrent.

Certaines classes incluses dans l’espace de noms System.Collections.Concurrent sont BlockingCollection<T>, ConcurrentDictionary<TKey,TValue>, ConcurrentQueue<T> et ConcurrentStack<T>.

Classes System.Collections

Les classes de l’espace de noms System.Collections ne stockent pas les éléments comme des objets spécifiquement typés, mais comme des objets de type Object.

Si possible, vous devez utiliser les collections génériques dans l’espace de noms System.Collections.Generic ou System.Collections.Concurrent à la place des types hérités de l’espace de noms System.Collections.

Le tableau suivant répertorie certaines des classes fréquemment utilisées de l’espace de noms System.Collections :

Classe Description
ArrayList Représente un tableau d’objets dont la taille est augmentée de manière dynamique selon les besoins.
Hashtable Représente une collection de paires clé/valeur qui sont organisées en fonction du code de hachage de la clé.
Queue Représente une collection d’objets premier entré, premier sorti (FIFO).
Stack Représente une collection d’objets dernier entré, premier sorti (LIFO).

L’espace de noms System.Collections.Specialized fournit des classes de collection spécialisées et fortement typées, telles que les collections à chaîne unique et les dictionnaires de liste liée et hybrides.

Implémentation d’une collection de paires clé/valeur

La collection générique Dictionary<TKey,TValue> vous permet d’accéder aux éléments d’une collection à l’aide de la clé de chaque élément. Chaque ajout au dictionnaire se compose d’une valeur et de sa clé associée. La récupération d’une valeur à l’aide de sa clé est très rapide, car la classe Dictionary est implémentée en tant que table de hachage.

L’exemple suivant crée une collection Dictionary et itère au sein du dictionnaire à l’aide d’une instruction foreach.

private static void IterateThruDictionary()
{
    Dictionary<string, Element> elements = BuildDictionary();

    foreach (KeyValuePair<string, Element> kvp in elements)
    {
        Element theElement = kvp.Value;

        Console.WriteLine("key: " + kvp.Key);
        Console.WriteLine("values: " + theElement.Symbol + " " +
            theElement.Name + " " + theElement.AtomicNumber);
    }
}

private static Dictionary<string, Element> BuildDictionary()
{
    var elements = new Dictionary<string, Element>();

    AddToDictionary(elements, "K", "Potassium", 19);
    AddToDictionary(elements, "Ca", "Calcium", 20);
    AddToDictionary(elements, "Sc", "Scandium", 21);
    AddToDictionary(elements, "Ti", "Titanium", 22);

    return elements;
}

private static void AddToDictionary(Dictionary<string, Element> elements,
    string symbol, string name, int atomicNumber)
{
    Element theElement = new Element();

    theElement.Symbol = symbol;
    theElement.Name = name;
    theElement.AtomicNumber = atomicNumber;

    elements.Add(key: theElement.Symbol, value: theElement);
}

public class Element
{
    public string Symbol { get; set; }
    public string Name { get; set; }
    public int AtomicNumber { get; set; }
}

Au lieu d’utiliser un initialiseur de collection pour générer la collection Dictionary, vous pouvez remplacer les méthodes BuildDictionary et AddToDictionary par la méthode suivante.

private static Dictionary<string, Element> BuildDictionary2()
{
    return new Dictionary<string, Element>
    {
        {"K",
            new Element() { Symbol="K", Name="Potassium", AtomicNumber=19}},
        {"Ca",
            new Element() { Symbol="Ca", Name="Calcium", AtomicNumber=20}},
        {"Sc",
            new Element() { Symbol="Sc", Name="Scandium", AtomicNumber=21}},
        {"Ti",
            new Element() { Symbol="Ti", Name="Titanium", AtomicNumber=22}}
    };
}

L’exemple suivant utilise la méthode ContainsKey et la propriété Item[] de Dictionary pour rechercher rapidement un élément par clé. En C#, la propriété Item vous permet d’accéder à un élément de la collection elements à l’aide d’elements[symbol].

private static void FindInDictionary(string symbol)
{
    Dictionary<string, Element> elements = BuildDictionary();

    if (elements.ContainsKey(symbol) == false)
    {
        Console.WriteLine(symbol + " not found");
    }
    else
    {
        Element theElement = elements[symbol];
        Console.WriteLine("found: " + theElement.Name);
    }
}

L’exemple suivant utilise à la place la méthode TryGetValue pour rechercher rapidement un élément par clé.

private static void FindInDictionary2(string symbol)
{
    Dictionary<string, Element> elements = BuildDictionary();

    Element theElement = null;
    if (elements.TryGetValue(symbol, out theElement) == false)
        Console.WriteLine(symbol + " not found");
    else
        Console.WriteLine("found: " + theElement.Name);
}

Utilisation de LINQ pour accéder à une collection

LINQ (Language-Integrated Query) peut être utilisé pour accéder aux collections. Les requêtes LINQ fournissent des fonctionnalités de filtrage, de classement et de regroupement. Pour plus d’informations, consultez prise en main avec LINQ en C#.

L’exemple suivant exécute une requête LINQ sur un List générique. La requête LINQ retourne une autre collection qui contient les résultats.

private static void ShowLINQ()
{
    List<Element> elements = BuildList();

    // LINQ Query.
    var subset = from theElement in elements
                 where theElement.AtomicNumber < 22
                 orderby theElement.Name
                 select theElement;

    foreach (Element theElement in subset)
    {
        Console.WriteLine(theElement.Name + " " + theElement.AtomicNumber);
    }

    // Output:
    //  Calcium 20
    //  Potassium 19
    //  Scandium 21
}

private static List<Element> BuildList()
{
    return new List<Element>
    {
        { new Element() { Symbol="K", Name="Potassium", AtomicNumber=19}},
        { new Element() { Symbol="Ca", Name="Calcium", AtomicNumber=20}},
        { new Element() { Symbol="Sc", Name="Scandium", AtomicNumber=21}},
        { new Element() { Symbol="Ti", Name="Titanium", AtomicNumber=22}}
    };
}

public class Element
{
    public string Symbol { get; set; }
    public string Name { get; set; }
    public int AtomicNumber { get; set; }
}

Tri d’une collection

L’exemple suivant illustre une procédure de tri d’une collection. L’exemple trie les instances de la classe Car stockées dans un List<T>. La classe Car implémente l’interface IComparable<T>, ce qui implique l’implémentation de la méthode CompareTo.

Chaque appel à la méthode CompareTo effectue une comparaison unique qui est utilisée pour le tri. Le code écrit par l’utilisateur dans la méthode CompareTo retourne une valeur pour chaque comparaison de l’objet actuel avec un autre objet. La valeur retournée est inférieure à zéro si l’objet actuel est inférieur à l’autre objet, supérieure à zéro l’objet actuel est supérieur à l’autre et égale à zéro s’ils sont égaux. Cela vous permet de définir dans le code les critères définissant « supérieur à », « inférieur à » et « égal à ».

Dans la méthode ListCars, l’instruction cars.Sort() trie la liste. Cet appel à la méthode Sort de List<T> entraîne l’appel automatique de la méthode CompareTo pour les objets Car dans List.

private static void ListCars()
{
    var cars = new List<Car>
    {
        { new Car() { Name = "car1", Color = "blue", Speed = 20}},
        { new Car() { Name = "car2", Color = "red", Speed = 50}},
        { new Car() { Name = "car3", Color = "green", Speed = 10}},
        { new Car() { Name = "car4", Color = "blue", Speed = 50}},
        { new Car() { Name = "car5", Color = "blue", Speed = 30}},
        { new Car() { Name = "car6", Color = "red", Speed = 60}},
        { new Car() { Name = "car7", Color = "green", Speed = 50}}
    };

    // Sort the cars by color alphabetically, and then by speed
    // in descending order.
    cars.Sort();

    // View all of the cars.
    foreach (Car thisCar in cars)
    {
        Console.Write(thisCar.Color.PadRight(5) + " ");
        Console.Write(thisCar.Speed.ToString() + " ");
        Console.Write(thisCar.Name);
        Console.WriteLine();
    }

    // Output:
    //  blue  50 car4
    //  blue  30 car5
    //  blue  20 car1
    //  green 50 car7
    //  green 10 car3
    //  red   60 car6
    //  red   50 car2
}

public class Car : IComparable<Car>
{
    public string Name { get; set; }
    public int Speed { get; set; }
    public string Color { get; set; }

    public int CompareTo(Car other)
    {
        // A call to this method makes a single comparison that is
        // used for sorting.

        // Determine the relative order of the objects being compared.
        // Sort by color alphabetically, and then by speed in
        // descending order.

        // Compare the colors.
        int compare;
        compare = String.Compare(this.Color, other.Color, true);

        // If the colors are the same, compare the speeds.
        if (compare == 0)
        {
            compare = this.Speed.CompareTo(other.Speed);

            // Use descending order for speed.
            compare = -compare;
        }

        return compare;
    }
}

Définition d’une collection personnalisée

Vous pouvez définir une collection en implémentant l’interface IEnumerable<T> ou IEnumerable.

Bien que vous puissiez définir une collection personnalisée, il est généralement préférable d’utiliser les collections incluses dans .NET, qui sont décrites dans genres de collections , plus haut dans cet article.

L’exemple suivant définit une classe de collection personnalisée nommée AllColors. Cette classe implémente l’interface IEnumerable, ce qui implique l’implémentation de la méthode GetEnumerator.

La méthode GetEnumerator retourne une instance de la classe ColorEnumerator. ColorEnumerator implémente l’interface IEnumerator, ce qui implique l’implémentation de la propriété Current, la méthode MoveNext et la méthode Reset.

private static void ListColors()
{
    var colors = new AllColors();

    foreach (Color theColor in colors)
    {
        Console.Write(theColor.Name + " ");
    }
    Console.WriteLine();
    // Output: red blue green
}

// Collection class.
public class AllColors : System.Collections.IEnumerable
{
    Color[] _colors =
    {
        new Color() { Name = "red" },
        new Color() { Name = "blue" },
        new Color() { Name = "green" }
    };

    public System.Collections.IEnumerator GetEnumerator()
    {
        return new ColorEnumerator(_colors);

        // Instead of creating a custom enumerator, you could
        // use the GetEnumerator of the array.
        //return _colors.GetEnumerator();
    }

    // Custom enumerator.
    private class ColorEnumerator : System.Collections.IEnumerator
    {
        private Color[] _colors;
        private int _position = -1;

        public ColorEnumerator(Color[] colors)
        {
            _colors = colors;
        }

        object System.Collections.IEnumerator.Current
        {
            get
            {
                return _colors[_position];
            }
        }

        bool System.Collections.IEnumerator.MoveNext()
        {
            _position++;
            return (_position < _colors.Length);
        }

        void System.Collections.IEnumerator.Reset()
        {
            _position = -1;
        }
    }
}

// Element class.
public class Color
{
    public string Name { get; set; }
}

Iterators

Un itérateur est utilisé pour exécuter une itération personnalisée sur une collection. Un itérateur peut être une méthode ou un accesseur get. Un itérateur utilise une instruction yield return pour retourner chaque élément de la collection un par un.

Vous appelez un itérateur en utilisant une instruction foreach. Chaque itération de la boucle foreach appelle l’itérateur. Quand une instruction yield return est atteinte dans l’itérateur, une expression est retournée et la localisation actuelle dans le code est retenue. L’exécution est redémarrée à partir de cet emplacement la prochaine fois que l’itérateur est appelé.

Pour plus d’informations, consultez Itérateurs (C#).

L'exemple suivant utilise une méthode Iterator. La méthode Iterator a une yield return instruction qui se trouve à l’intérieur d’une for boucle. Dans la méthode ListEvenNumbers, chaque itération du corps de l’instruction foreach crée un appel à la méthode Iterator, qui continue sur l’instruction yield return suivante.

private static void ListEvenNumbers()
{
    foreach (int number in EvenSequence(5, 18))
    {
        Console.Write(number.ToString() + " ");
    }
    Console.WriteLine();
    // Output: 6 8 10 12 14 16 18
}

private static IEnumerable<int> EvenSequence(
    int firstNumber, int lastNumber)
{
    // Yield even numbers in the range.
    for (var number = firstNumber; number <= lastNumber; number++)
    {
        if (number % 2 == 0)
        {
            yield return number;
        }
    }
}

Voir aussi