À quoi sert la classe ArraySegment <T>?

97

Je viens de tomber sur le ArraySegment<byte>type en sous-classant la MessageEncoderclasse.

Je comprends maintenant que c'est un segment d'un tableau donné, prend un décalage, n'est pas énumérable et n'a pas d'indexeur, mais je ne comprends toujours pas son utilisation. Quelqu'un peut-il expliquer avec un exemple?

stackoverflowuser
la source
8
Il semble être ArraySegmenténumérable dans .Net 4.5.
svick
Pour tenter comme cette question ..
Ken Kin

Réponses:

55

ArraySegment<T>est devenu beaucoup plus utile dans .NET 4.5 + et .NET Core car il implémente désormais:

  • IList<T>
  • ICollection<T>
  • IEnumerable<T>
  • IEnumerable
  • IReadOnlyList<T>
  • IReadOnlyCollection<T>

contrairement à la version .NET 4 qui n'implémentait aucune interface.

La classe est maintenant capable de prendre part au monde merveilleux de LINQ afin que nous puissions faire les choses LINQ habituelles comme interroger le contenu, inverser le contenu sans affecter le tableau d'origine, obtenir le premier élément, et ainsi de suite:

var array = new byte[] { 5, 8, 9, 20, 70, 44, 2, 4 };
array.Dump();
var segment = new ArraySegment<byte>(array, 2, 3);
segment.Dump(); // output: 9, 20, 70
segment.Reverse().Dump(); // output 70, 20, 9
segment.Any(s => s == 99).Dump(); // output false
segment.First().Dump(); // output 9
array.Dump(); // no change
Stephen Kennedy
la source
4
Bien qu'ils soient inexplicablement rendus GetEnumeratorprivés, ce qui signifie que vous êtes obligé de lancer IEnumerable<T>(une conversion de boxe) pour l'appeler. Pouah!
BlueRaja - Danny Pflughoeft
27
  1. Partitionnement de la mémoire tampon pour les classes IO - Utilisez le même tampon pour les opérations de lecture et d'écriture simultanées et disposez d'une structure unique que vous pouvez transmettre pour décrire l'ensemble de votre opération.
  2. Définir les fonctions - Mathématiquement parlant, vous pouvez représenter n'importe quel sous-ensemble contigu en utilisant cette nouvelle structure. Cela signifie essentiellement que vous pouvez créer des partitions du tableau, mais vous ne pouvez pas représenter toutes les cotes et tous les évens. Notez que le teaser de téléphone proposé par The1 aurait pu être résolu avec élégance en utilisant le partitionnement ArraySegment et une structure arborescente. Les nombres finaux auraient pu être écrits en parcourant d'abord la profondeur de l'arbre. Cela aurait été un scénario idéal en termes de mémoire et de vitesse je crois.
  3. Multithreading - Vous pouvez désormais générer plusieurs threads pour fonctionner sur la même source de données tout en utilisant des tableaux segmentés comme porte de contrôle. Les boucles qui utilisent des calculs discrets peuvent maintenant être fermées assez facilement, ce que les derniers compilateurs C ++ commencent à faire comme étape d'optimisation du code.
  4. Segmentation de l'interface utilisateur - Contraignez les affichages de votre interface utilisateur à l'aide de structures segmentées. Vous pouvez désormais stocker des structures représentant des pages de données qui peuvent être rapidement appliquées aux fonctions d'affichage. Des tableaux contigus simples peuvent être utilisés afin d'afficher des vues discrètes, ou même des structures hiérarchiques telles que les nœuds dans un TreeView en segmentant un magasin de données linéaire en segments de collection de nœuds.

Dans cet exemple, nous examinons comment vous pouvez utiliser le tableau d'origine, les propriétés Offset et Count, ainsi que comment vous pouvez parcourir les éléments spécifiés dans ArraySegment.

using System;

class Program
{
    static void Main()
    {
        // Create an ArraySegment from this array.
        int[] array = { 10, 20, 30 };
        ArraySegment<int> segment = new ArraySegment<int>(array, 1, 2);

        // Write the array.
        Console.WriteLine("-- Array --");
        int[] original = segment.Array;
        foreach (int value in original)
        {
            Console.WriteLine(value);
        }

        // Write the offset.
        Console.WriteLine("-- Offset --");
        Console.WriteLine(segment.Offset);

        // Write the count.
        Console.WriteLine("-- Count --");
        Console.WriteLine(segment.Count);

        // Write the elements in the range specified in the ArraySegment.
        Console.WriteLine("-- Range --");
        for (int i = segment.Offset; i < segment.Count+segment.Offset; i++)
        {
            Console.WriteLine(segment.Array[i]);
        }
    }
}

ArraySegment Structure - à quoi pensaient-ils?

Greg McNulty
la source
3
ArraySegment n'est qu'une structure. Ma meilleure hypothèse est que son but est de permettre à un segment d'un tableau d'être transmis sans avoir à en faire une copie.
Brian
1
Je pense que la déclaration de condition de la boucle for devrait être i < segment.Offset + segment.Count.
Eren Ersönmez
1
+1 pour les faits que vous avez mentionnés, mais @Eren a raison: vous ne pouvez pas itérer les éléments d'un segment comme ça.
Şafak Gür
3
Il est généralement approprié de donner une attribution lorsque vous utilisez un autre code. C'est juste de bonnes manières. Votre exemple provient de dotnetperls.com/arraysegment .
1
À moins bien sûr qu'ils ne l'aient emprunté à votre réponse. Dans ce cas, ils devraient vous donner des crédits. :)
26

C'est une petite structure de soldat chétive qui ne fait rien d'autre que garder une référence à un tableau et stocke une plage d'index. Un peu dangereux, sachez qu'il ne fait pas de copie des données du tableau et ne rend en aucun cas le tableau immuable ni n'exprime le besoin d'immuabilité. Le modèle de programmation le plus typique consiste simplement à conserver ou à transmettre le tableau et une variable ou un paramètre de longueur, comme cela se fait dans les méthodes .NET BeginRead (), String.SubString (), Encoding.GetString (), etc., etc.

Il n'est pas beaucoup utilisé dans le .NET Framework, sauf pour ce qui semble être un programmeur Microsoft particulier qui a travaillé sur les sockets Web et WCF l'aimant. Ce qui est probablement le bon guide, si vous l'aimez, utilisez-le. Il a fait un coup d'oeil dans .NET 4.6, la méthode supplémentaire MemoryStream.TryGetBuffer () l'utilise. outJe préfère avoir deux arguments que je suppose.

En général, la notion plus universelle de tranches figure en bonne place sur la liste de souhaits des principaux ingénieurs .NET comme Mads Torgersen et Stephen Toub. Ce dernier a lancé la array[:]proposition de syntaxe il y a quelque temps, vous pouvez voir ce à quoi ils ont pensé dans cette page Roslyn . Je suppose que cela dépend en fin de compte d'obtenir le support CLR. Ceci est activement réfléchi pour la version 7 de C # afaik, gardez un œil sur System.Slices .

Mise à jour: lien mort, livré dans la version 7.2 en tant que Span .

Update2: plus de support dans C # version 8.0 avec les types Range et Index et une méthode Slice ().

Hans Passant
la source
"Ce n'est pas très utile '- je l'ai trouvé incroyablement utile dans un système qui nécessitait malheureusement des micro-optimisations en raison d'une limitation de la mémoire. Le fait qu'il existe également d' autres solutions" typiques "n'enlève rien à son utilité
AaronHS
5
D'accord, d'accord, je n'ai pas vraiment besoin d'un témoignage de tous ceux qui ont pris l'habitude de l'utiliser :) Le mieux est de voter pour le commentaire de @ CRice. Comme indiqué, "si vous l'aimez, utilisez-le". Alors utilisez-le. Les tranches seront géniales, j'ai hâte.
Hans Passant le
Il existe un ReadOnlySpan pour ces puristes immuables.
Arek Bal
7

Qu'en est-il d'une classe wrapper? Juste pour éviter de copier des données dans des tampons temporels.

public class SubArray<T> {
        private ArraySegment<T> segment;

        public SubArray(T[] array, int offset, int count) {
            segment = new ArraySegment<T>(array, offset, count);
        }
        public int Count {
            get { return segment.Count; }
        }

        public T this[int index] {
            get {
               return segment.Array[segment.Offset + index];
            }
        }

        public T[] ToArray() {
            T[] temp = new T[segment.Count];
            Array.Copy(segment.Array, segment.Offset, temp, 0, segment.Count);
            return temp;
        }

        public IEnumerator<T> GetEnumerator() {
            for (int i = segment.Offset; i < segment.Offset + segment.Count; i++) {
                yield return segment.Array[i];
            }
        }
    } //end of the class

Exemple:

byte[] pp = new byte[] { 1, 2, 3, 4 };
SubArray<byte> sa = new SubArray<byte>(pp, 2, 2);

Console.WriteLine(sa[0]);
Console.WriteLine(sa[1]);
//Console.WriteLine(b[2]); exception

Console.WriteLine();
foreach (byte b in sa) {
    Console.WriteLine(b);
}

Sortie:

3
4

3
4
nergeia
la source
Copain très utile, merci, notez que vous pouvez le faire implémenter IEnumerable<T>puis ajouter IEnumeratorIEnumerable.GetEnumerator() { return GetEnumerator(); }
MaYaN
5

Le ArraySegment est BEAUCOUP plus utile que vous ne le pensez. Essayez d'exécuter le test unitaire suivant et préparez-vous à être surpris!

    [TestMethod]
    public void ArraySegmentMagic()
    {
        var arr = new[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

        var arrSegs = new ArraySegment<int>[3];
        arrSegs[0] = new ArraySegment<int>(arr, 0, 3);
        arrSegs[1] = new ArraySegment<int>(arr, 3, 3);
        arrSegs[2] = new ArraySegment<int>(arr, 6, 3);
        for (var i = 0; i < 3; i++)
        {
            var seg = arrSegs[i] as IList<int>;
            Console.Write(seg.GetType().Name.Substring(0, 12) + i);
            Console.Write(" {");
            for (var j = 0; j < seg.Count; j++)
            {
                Console.Write("{0},", seg[j]);
            }
            Console.WriteLine("}");
        }
    }

Vous voyez, tout ce que vous avez à faire est de convertir un ArraySegment en IList et il fera toutes les choses que vous attendiez probablement qu'il fasse en premier lieu. Notez que le type est toujours ArraySegment, même s'il se comporte comme une liste normale.

PRODUCTION:

ArraySegment0 {0,1,2,}
ArraySegment1 {3,4,5,}
ArraySegment2 {6,7,8,}
Ben Stabile
la source
4
C'est dommage qu'il soit nécessaire de le lancer IList<T>. Je m'attendrais à ce que l'indexeur le soit public.
xmedeko
2
Pour tous ceux qui trouvent cette réponse et pensent que c'est une solution miracle, je recommande d'abord de considérer vos besoins en termes de performances et de comparer cela à un accès direct au tableau d'origine en utilisant les contraintes d'index du segment du tableau. La conversion en IList nécessite des appels de méthode ultérieurs (y compris l'indexeur) pour passer par l'interface IList avant d'atteindre l'implémentation. Il y a beaucoup de discussions sur Internet où les gens parlent du coût de performance de l'utilisation d'appels abstraits en boucles serrées. Lire ici: github.com/dotnet/coreclr/issues/9105
JamesHoux
3

En termes simples: il garde la référence à un tableau, vous permettant d'avoir plusieurs références à une seule variable de tableau, chacune avec une plage différente.

En fait, cela vous aide à utiliser et à transmettre des sections d'un tableau d'une manière plus structurée, au lieu d'avoir plusieurs variables, pour contenir l'index de début et la longueur. Il fournit également des interfaces de collecte pour travailler plus facilement avec les sections de tableau.

Par exemple, les deux exemples de code suivants font la même chose, l'un avec ArraySegment et l'autre sans:

        byte[] arr1 = new byte[] { 1, 2, 3, 4, 5, 6 };
        ArraySegment<byte> seg1 = new ArraySegment<byte>(arr1, 2, 2);
        MessageBox.Show((seg1 as IList<byte>)[0].ToString());

et,

        byte[] arr1 = new byte[] { 1, 2, 3, 4, 5, 6 };
        int offset = 2;
        int length = 2;
        byte[] arr2 = arr1;
        MessageBox.Show(arr2[offset + 0].ToString());

De toute évidence, le premier extrait de code est préférable, en particulier lorsque vous souhaitez transmettre des segments de tableau à une fonction.

M.Mahdipour
la source