c# données - Quand utiliser struct?




14 Answers

La source référencée par l'OP a une certaine crédibilité ... mais qu'en est-il de Microsoft - quelle est la position sur l'utilisation de la structure? J'ai cherché un complément d' apprentissage auprès de Microsoft , et voici ce que j'ai trouvé:

Envisagez de définir une structure au lieu d'une classe si les instances de ce type sont petites et généralement de courte durée ou si elles sont généralement intégrées à d'autres objets.

Ne définissez pas une structure à moins que le type ne possède toutes les caractéristiques suivantes:

  1. Il représente logiquement une seule valeur, similaire aux types primitifs (entier, double, etc.).
  2. Il a une taille d'instance inférieure à 16 octets.
  3. C'est immuable.
  4. Il n'aura pas à être encadré fréquemment.

Microsoft viole systématiquement ces règles

D'accord, # 2 et # 3 de toute façon. Notre dictionnaire bien-aimé a deux structures internes:

[StructLayout(LayoutKind.Sequential)]  // default for structs
private struct Entry  //<Tkey, TValue>
{
    //  View code at *Reference Source
}

[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator : 
    IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable, 
    IDictionaryEnumerator, IEnumerator
{
    //  View code at *Reference Source
}

* Source de référence

La source 'JonnyCantCode.com' a obtenu 3 sur 4 - pardonnable puisque le numéro 4 ne serait probablement pas un problème. Si vous vous trouvez en train de boxer une structure, repensez votre architecture.

Regardons pourquoi Microsoft utiliserait ces structures:

  1. Chaque struct, Entry et Enumerator , représentent des valeurs uniques.
  2. La vitesse
  3. Entry n'est jamais passée en tant que paramètre en dehors de la classe Dictionary. Une recherche plus poussée montre que, pour satisfaire l'implémentation de IEnumerable, Dictionary utilise la structure Enumerator qu'il copie chaque fois qu'un enquêteur est demandé ... a du sens.
  4. Interne à la classe Dictionary. Enumerator est public car le dictionnaire est énumérable et doit avoir une accessibilité égale à l'implémentation de l'interface IEnumerator - par exemple, getter IEnumerator.

Mise à jour - De plus, réalisez que lorsqu'une structure implémente une interface - comme le fait Enumerator - et qu'elle est castée dans ce type implémenté, la structure devient un type de référence et est déplacée vers le tas. Interne à la classe Dictionary, Enumerator est toujours un type de valeur. Cependant, dès qu'une méthode appelle GetEnumerator() , un IEnumerator type référence est renvoyé.

Ce que nous ne voyons pas ici est une tentative ou une preuve de l'obligation de garder les structures immuables ou de maintenir une taille d'instance de seulement 16 octets ou moins:

  1. Rien dans les structs ci-dessus n'est déclaré readonly - pas immuable
  2. La taille de ces structures pourrait bien dépasser 16 octets
  3. Entry a une durée de vie indéterminée (depuis Add() , Remove() , Clear() ou Garbage Collection);

Et ... 4. Les deux structures stockent TKey et TValue, que nous savons tous sont tout à fait capables d'être des types de référence (info bonus ajouté)

Malgré les clés hachées, les dictionnaires sont rapides en partie parce que l'instanciation d'une structure est plus rapide qu'un type de référence. Ici, j'ai un Dictionary<int, int> qui stocke 300 000 entiers aléatoires avec des clés incrémentées séquentiellement.

Capacité: 312874
MemSize: 2660827 octets
Redimensionnement terminé: 5ms
Temps total à remplir: 889ms

Capacité : le nombre d'éléments disponibles avant le tableau interne doit être redimensionné.

MemSize : déterminé en sérialisant le dictionnaire dans un MemoryStream et en obtenant une longueur d'octet (assez précise pour nos besoins).

Redimensionnement terminé : le temps nécessaire pour redimensionner le tableau interne de 150862 éléments en 312874 éléments. Lorsque vous vous Array.CopyTo() que chaque élément est copié séquentiellement via Array.CopyTo() , ce n'est pas trop minable.

Temps total à remplir : il est OnResize un biais dû à la journalisation et à un événement OnResize j'ai ajouté à la source; Cependant, toujours impressionnant de remplir 300k entiers tout en redimensionnant 15 fois pendant l'opération. Juste par curiosité, quel serait le temps total à remplir si je connaissais déjà la capacité? 13ms

Alors, maintenant, que si l' Entry était une classe? Est-ce que ces temps ou métriques différeraient vraiment beaucoup?

Capacité: 312874
MemSize: 2660827 octets
Redimensionnement terminé: 26ms
Temps total à remplir: 964ms

Évidemment, la grande différence est dans le redimensionnement. Toute différence si le dictionnaire est initialisé avec la capacité? Pas assez pour être concerné par ... 12ms .

Qu'est-ce qui se passe est, parce que l' Entry est une struct, il ne nécessite pas d'initialisation comme un type de référence. C'est à la fois la beauté et le fléau du type de valeur. Afin d'utiliser Entry comme type de référence, j'ai dû insérer le code suivant:

/*
 *  Added to satisfy initialization of entry elements --
 *  this is where the extra time is spent resizing the Entry array
 * **/
for (int i = 0 ; i < prime ; i++)
{
    destinationArray[i] = new Entry( );
}
/*  *********************************************** */  

La raison pour laquelle j'ai dû initialiser chaque élément de tableau d' Entry tant que type de référence peut être trouvée sur MSDN: Structure Design . En bref:

Ne fournissez pas un constructeur par défaut pour une structure.

Si une structure définit un constructeur par défaut, lorsque des tableaux de la structure sont créés, le Common Language Runtime exécute automatiquement le constructeur par défaut sur chaque élément de tableau.

Certains compilateurs, tels que le compilateur C #, n'autorisent pas les structures à avoir des constructeurs par défaut.

C'est en fait assez simple et nous emprunterons aux trois lois de la robotique d'Asimov :

  1. La structure doit être sûre à utiliser
  2. La structure doit remplir sa fonction efficacement, à moins que cela ne viole la règle # 1
  3. La structure doit rester intacte pendant son utilisation à moins que sa destruction ne soit requise pour satisfaire à la règle # 1

... que retirons-nous de cela : en somme, soyez responsable de l'utilisation des types de valeur. Ils sont rapides et efficaces, mais ont la capacité de provoquer de nombreux comportements inattendus s'ils ne sont pas correctement entretenus (c'est-à-dire des copies involontaires).

structure de

Quand devriez-vous utiliser struct et non class en C #? Mon modèle conceptuel est que les structs sont utilisés dans les moments où l'article est simplement une collection de types de valeur . Une façon de les loger tous ensemble dans un ensemble cohérent.

Je suis tombé sur ces règles here :

  • Une structure devrait représenter une seule valeur.
  • Une structure doit avoir une empreinte mémoire inférieure à 16 octets.
  • Une structure ne doit pas être modifiée après la création.

Est-ce que ces règles fonctionnent? Qu'est-ce qu'une structure signifie sémantiquement?




Je ne suis pas d'accord avec les règles données dans le message original. Voici mes règles:

1) Vous utilisez des structures pour les performances lorsque vous les stockez dans des tableaux. (voir aussi Quand structs la réponse? )

2) Vous en avez besoin dans le code en passant des données structurées de / vers C / C ++

3) N'utilisez pas de structures sauf si vous en avez besoin:

  • Ils se comportent différemment des "objets normaux" ( types de référence ) sous assignation et lors du passage en arguments, ce qui peut conduire à un comportement inattendu; Ceci est particulièrement dangereux si la personne qui regarde le code ne sait pas qu'il s'agit d'une structure.
  • Ils ne peuvent pas être hérités.
  • Passer des structures comme arguments est plus cher que les classes.



En plus de la réponse "c'est une valeur", un scénario spécifique pour l'utilisation de structures est lorsque vous savez que vous avez un ensemble de données qui provoque des problèmes de récupération de place, et vous avez beaucoup d'objets. Par exemple, une liste / tableau important d'instances de personnes. La métaphore naturelle est ici une classe, mais si vous avez un grand nombre d'instances de personnes ayant une longue durée de vie, elles peuvent finir par obstruer GEN-2 et causer des décrochages au GC. Si le scénario le justifie, une approche potentielle consiste à utiliser un tableau (et non une liste) de structures de personnes, c'est-à-dire Person[] . Maintenant, au lieu d'avoir des millions d'objets dans GEN-2, vous avez un seul morceau sur le LOH (je suppose qu'il n'y a pas de chaînes ici, c'est-à-dire une pure valeur sans aucune référence). Cela a très peu d'impact sur le GC.

Travailler avec ces données est gênant, car les données sont probablement surdimensionnées pour une structure, et vous ne voulez pas copier les valeurs de graisse tout le temps. Cependant, l'accès direct dans un tableau ne copie pas la structure - il est en place (contrairement à un indexeur de liste, qui copie). Cela signifie beaucoup de travail avec les index:

int index = ...
int id = peopleArray[index].Id;

Notez que garder les valeurs elles-mêmes immuables aidera ici. Pour une logique plus complexe, utilisez une méthode avec un paramètre by-ref:

void Foo(ref Person person) {...}
...
Foo(ref peopleArray[index]);

Encore une fois, c'est en place - nous n'avons pas copié la valeur.

Dans des scénarios très spécifiques, cette tactique peut être très efficace. Cependant, c'est un scernario assez avancé qui devrait être tenté seulement si vous savez ce que vous faites et pourquoi. Le défaut ici serait une classe.




Les structs sont bons pour la représentation atomique des données, où lesdites données peuvent être copiées plusieurs fois par le code. Le clonage d'un objet est en général plus onéreux que la copie d'une structure, car il implique l'allocation de la mémoire, l'exécution du constructeur et la désallocation / garbage collection une fois terminé.




Premièrement: scénarios d'interopérabilité ou lorsque vous devez spécifier la disposition de la mémoire

Deuxièmement: Lorsque les données sont presque de la même taille qu'un pointeur de référence de toute façon.




J'utilise des structures pour emballer ou déballer n'importe quel format de communication binaire. Cela inclut la lecture ou l'écriture sur disque, les listes de vertex DirectX, les protocoles réseau ou le traitement de données cryptées / compressées.

Les trois directives que vous énumérez ne m'ont pas été utiles dans ce contexte. Quand j'ai besoin d'écrire quatre cent octets de choses dans un ordre particulier, je vais définir une structure de quatre cent octets, et je vais la remplir avec toutes les valeurs indépendantes qu'elle est censée avoir, et je vais pour le mettre en place de toute façon fait le plus de sens à la fois. (Ok, quatre cents octets serait assez étrange - mais quand j'écrivais des fichiers Excel pour gagner ma vie, je travaillais avec des structs d'environ quarante octets partout, parce que c'est la taille de certains des enregistrements BIFF.)




.NET prend en charge les value types et reference types (en Java, vous ne pouvez définir que des types de référence). Les instances de reference types de reference types sont allouées dans le tas géré et sont collectées sans aucune référence en suspens. D'autre part, les instances de value types de value types sont allouées dans la stack , et par conséquent la mémoire allouée est récupérée dès que leur portée se termine. Et bien sûr, les value types sont transmis par valeur, et reference types de référence par référence. Tous les types de données primitives C #, à l'exception de System.String, sont des types de valeur.

Quand utiliser struct sur classe,

En C #, les structs sont des value types , les classes sont reference types . Vous pouvez créer des types de valeur, en C #, en utilisant le mot clé enum et le mot struct clé struct . L'utilisation d'un value type au lieu d'un reference type réduit le nombre d'objets sur le tas géré, ce qui entraîne une charge moindre sur le garbage collector (GC), des cycles GC moins fréquents et, par conséquent, de meilleures performances. Cependant, les value types ont aussi leurs inconvénients. Passer autour d'une grande struct est certainement plus coûteux que de passer une référence, c'est un problème évident. L'autre problème est l'overhead associé à la boxing/unboxing . Dans le cas où vous vous demandez ce que signifie boxing/unboxing , suivez ces liens pour une bonne explication sur la boxing et le unboxing . En dehors de la performance, il y a des moments où vous avez simplement besoin de types pour avoir une sémantique de valeur, ce qui serait très difficile (ou moche) à implémenter si reference types sont tout ce que vous avez. Vous ne devez utiliser value types . Lorsque vous avez besoin de copier la sémantique ou que vous avez besoin d'une initialisation automatique, normalement dans des arrays de ces types.




Nah - I don't entirely agree with the rules. They are good guidelines to consider with performance and standardization, but not in light of the possibilities.

As you can see in the responses, there are a log of creative ways to use them. So, these guidelines need to just be that, always for the sake of performance and efficiency.

In this case, I use classes to represent real world objects in their larger form, I use structs to represent smaller objects that have more exact uses. The way you said it, "a more cohesive whole." The keyword being cohesive. The classes will be more object oriented elements, while structs can have some of those characteristics, their on a smaller scale. IMO.

I use them a lot putting in Treeview and Listview tags where common static attributes can be accessed very quickly. I would struggle to get this info another way. For example, in my database applications, I use a Treeview where I have Tables, SPs, Functions, or any other objects. I create and populate my struct, put it in the tag, pull it out, get the data of the selection and so forth. I wouldn't do this with a class!

I do try and keep them small, use them in single instance situations, and keep them from changing. It's prudent to be aware of memory, allocation, and performance. And testing is so necessary.




A class is a reference type. When an object of the class is created, the variable to which the object is assigned holds only a reference to that memory. When the object reference is assigned to a new variable, the new variable refers to the original object. Changes made through one variable are reflected in the other variable because they both refer to the same data. A struct is a value type. When a struct is created, the variable to which the struct is assigned holds the struct's actual data. When the struct is assigned to a new variable, it is copied. The new variable and the original variable therefore contain two separate copies of the same data. Changes made to one copy do not affect the other copy. In general, classes are used to model more complex behavior, or data that is intended to be modified after a class object is created. Structs are best suited for small data structures that contain primarily data that is not intended to be modified after the struct is created.

Classes and Structs (C# Programming Guide)




I did a small benchmark with BenchmarkDotNet to get a better understanding of "struct" benefit in numbers. I'm testing looping through array (or list) of structs (or classes). Creating those arrays or lists is out of the benchmark's scope - it is clear that "class" is more heavy will utilize more memory, and will involve GC.

So the conclusion is: be careful with LINQ and hidden structs boxing/unboxing and using structs for microoptimizations strictly stay with arrays.

PS Another benchmark about passing struct/class through call stack is there https://.com/a/47864451/506147

BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233542 Hz, Resolution=309.2584 ns, Timer=TSC
  [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Core   : .NET Core 4.6.25211.01, 64bit RyuJIT


          Method |  Job | Runtime |      Mean |     Error |    StdDev |       Min |       Max |    Median | Rank |  Gen 0 | Allocated |
---------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|----------:|-----:|-------:|----------:|
   TestListClass |  Clr |     Clr |  5.599 us | 0.0408 us | 0.0382 us |  5.561 us |  5.689 us |  5.583 us |    3 |      - |       0 B |
  TestArrayClass |  Clr |     Clr |  2.024 us | 0.0102 us | 0.0096 us |  2.011 us |  2.043 us |  2.022 us |    2 |      - |       0 B |
  TestListStruct |  Clr |     Clr |  8.427 us | 0.1983 us | 0.2204 us |  8.101 us |  9.007 us |  8.374 us |    5 |      - |       0 B |
 TestArrayStruct |  Clr |     Clr |  1.539 us | 0.0295 us | 0.0276 us |  1.502 us |  1.577 us |  1.537 us |    1 |      - |       0 B |
   TestLinqClass |  Clr |     Clr | 13.117 us | 0.1007 us | 0.0892 us | 13.007 us | 13.301 us | 13.089 us |    7 | 0.0153 |      80 B |
  TestLinqStruct |  Clr |     Clr | 28.676 us | 0.1837 us | 0.1534 us | 28.441 us | 28.957 us | 28.660 us |    9 |      - |      96 B |
   TestListClass | Core |    Core |  5.747 us | 0.1147 us | 0.1275 us |  5.567 us |  5.945 us |  5.756 us |    4 |      - |       0 B |
  TestArrayClass | Core |    Core |  2.023 us | 0.0299 us | 0.0279 us |  1.990 us |  2.069 us |  2.013 us |    2 |      - |       0 B |
  TestListStruct | Core |    Core |  8.753 us | 0.1659 us | 0.1910 us |  8.498 us |  9.110 us |  8.670 us |    6 |      - |       0 B |
 TestArrayStruct | Core |    Core |  1.552 us | 0.0307 us | 0.0377 us |  1.496 us |  1.618 us |  1.552 us |    1 |      - |       0 B |
   TestLinqClass | Core |    Core | 14.286 us | 0.2430 us | 0.2273 us | 13.956 us | 14.678 us | 14.313 us |    8 | 0.0153 |      72 B |
  TestLinqStruct | Core |    Core | 30.121 us | 0.5941 us | 0.5835 us | 28.928 us | 30.909 us | 30.153 us |   10 |      - |      88 B |

Code:

[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkRef
    {
        public class C1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        public struct S1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        List<C1> testListClass = new List<C1>();
        List<S1> testListStruct = new List<S1>();
        C1[] testArrayClass;
        S1[] testArrayStruct;
        public BenchmarkRef()
        {
            for(int i=0;i<1000;i++)
            {
                testListClass.Add(new C1  { Text1= i.ToString(), Text2=null, Text3= i.ToString() });
                testListStruct.Add(new S1 { Text1 = i.ToString(), Text2 = null, Text3 = i.ToString() });
            }
            testArrayClass = testListClass.ToArray();
            testArrayStruct = testListStruct.ToArray();
        }

        [Benchmark]
        public int TestListClass()
        {
            var x = 0;
            foreach(var i in testListClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayClass()
        {
            var x = 0;
            foreach (var i in testArrayClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestListStruct()
        {
            var x = 0;
            foreach (var i in testListStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayStruct()
        {
            var x = 0;
            foreach (var i in testArrayStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestLinqClass()
        {
            var x = testListClass.Select(i=> i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }

        [Benchmark]
        public int TestLinqStruct()
        {
            var x = testListStruct.Select(i => i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }
    }



I was just dealing with Windows Communication Foundation [WCF] Named Pipe and I did notice that it does make sense to use Structs in order to ensure that exchange of data is of value type instead of reference type .




Structure or value types can be used in following scenarios -

  1. If you want to prevent the object to be collected by garbage collection.
  2. If it is a simple type and no member function modifies its instance fields
  3. If there is no need to derive from other types or being derived to other types.

You can know more about the value types and values types here on this link




The C# struct is a lightweight alternative to a class. It can do almost the same as a class, but it's less "expensive" to use a struct rather than a class. The reason for this is a bit technical, but to sum up, new instances of a class is placed on the heap, where newly instantiated structs are placed on the stack. Furthermore, you are not dealing with references to structs, like with classes, but instead you are working directly with the struct instance. This also means that when you pass a struct to a function, it is by value, instead of as a reference. There is more about this in the chapter about function parameters.

So, you should use structs when you wish to represent more simple data structures, and especially if you know that you will be instantiating lots of them. There are lots of examples in the .NET framework, where Microsoft has used structs instead of classes, for instance the Point, Rectangle and Color struct.




It seems to me that struct have no strong semantic that give the user a strong idea of when to use it.

It resemble as a class but lake most of its feature. It is a kind of degraded version of a class. There is a lot of says about when not use it but very few on when to use it.

IMO, there is no reason why struct should be implemented in a OO language at the first place. Actually primitive type should not exist in a pure OO language but I digress.

It might be a way to optimize stuff. A kind of boxing free things that can be optimize on some call site.

My 2 cent, I would say that it violated the KISS of the language principle and avoid it as much has I can.




Related

c# struct

Tags

c#   struct