web-dev-qa-db-fra.com

Quand utiliser struct?

Quand faut-il utiliser struct et non class en C #? Mon modèle conceptuel est que les structures sont utilisées lorsque l'élément est simplement une collection de types de valeur . Une façon de les loger logiquement dans un tout cohérent.

Je suis tombé sur ces règles ici :

  • Un struct devrait représenter une valeur unique.
  • 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?

1328
Alex Baranosky

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 des structures? J'ai cherché des informations supplémentaires 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 ont généralement une durée de vie courte ou sont généralement intégrées à d'autres objets.

Ne définissez une structure que si le type présente toutes les caractéristiques suivantes:

  1. Il représente logiquement une valeur unique, similaire aux types primitifs (entier, double, etc.).
  2. Il a une taille d'instance inférieure à 16 octets.
  3. C'est immuable.
  4. Il ne sera pas nécessaire de le boxer fréquemment.

Microsoft enfreint systématiquement ces règles

D'accord, n ° 2 et n ° 3 quand même. Notre dictionnaire bien-aimé a 2 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 - tout à fait pardonnable puisque le numéro 4 ne serait probablement pas un problème. Si vous vous retrouvez en train de boxer une structure, repensez votre architecture.

Voyons pourquoi Microsoft utiliserait ces structures:

  1. Chaque structure, Entry et Enumerator, représente des valeurs uniques.
  2. La vitesse
  3. Entry n'est jamais passé en tant que paramètre en dehors de la classe Dictionary. Un examen plus approfondi montre que pour satisfaire à la mise en oeuvre de IEnumerable, Dictionary utilise la structure Enumerator qu'il copie à chaque fois qu'un énumérateur est demandé ... est logique.
  4. Interne à la classe Dictionary. Enumerator est public car Dictionary est énumérable et doit avoir le même accès à la mise en œuvre de l'interface IEnumerator - par exemple. IEnumerator getter.

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

Ce que nous ne voyons pas ici est une tentative ou une preuve d'exigence de conserver les structures immuables ou de conserver une taille d'instance de 16 octets ou moins:

  1. Rien dans les structures 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 (de Add(), à Remove(), Clear() ou au garbage collection);

Et ... 4. Les deux structures stockent TKey et TValue, que nous savons tous parfaitement capables d'être des types de référence (informations de bonus ajoutées)

Malgré les clés hachées, les dictionnaires sont rapides car 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
Terminé Redimensionner: 5ms
Temps total de remplissage: 889ms

Capacité : nombre d'éléments disponibles avant que le tableau interne ne soit redimensionné.

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

Complété Redimensionner : durée nécessaire au redimensionnement du tableau interne de 150862 éléments à 312874 éléments. Lorsque vous estimez que chaque élément est copié séquentiellement via Array.CopyTo(), vous ne vous inquiétez pas.

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

Alors, que se passe-t-il si Entry était une classe? Ces temps ou métriques seraient-ils vraiment si différents?

Capacité: 312874
MemSize: 2660827 octets
Terminé Redimensionner: 26ms
Temps total de remplissage: 964ms

De toute évidence, la grande différence réside dans le redimensionnement. Toute différence si le dictionnaire est initialisé avec la capacité? Pas assez pour se préoccuper de ... 12ms .

Comme Entry est une structure, 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 valeur. Pour 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 de Entry en tant que type de référence se trouve à l'adresse MSDN: Structure Design . En bref:

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

Si une structure définit un constructeur par défaut, lors de la création de tableaux de la structure, 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 allons emprunter de Trois lois de la robotique () = = d'Asimov:

  1. La structure doit être sûre à utiliser
  2. La structure doit remplir sa fonction efficacement, sauf si cela enfreint la règle 1
  3. La structure doit rester intacte pendant son utilisation sauf si sa destruction est nécessaire pour satisfaire à la règle 1

... Que retirons-nous de ceci : en bref, soyez responsable de l'utilisation de types de valeur. Ils sont rapides et efficaces, mais peuvent provoquer de nombreux comportements inattendus s’ils ne sont pas correctement entretenus (copies non intentionnelles, par exemple).

585
IAbstract

Chaque fois que vous n'avez pas besoin de polymorphisme, souhaitez une sémantique de valeur et évitez l'allocation de tas et la surcharge associée à la récupération de place. La mise en garde, cependant, est que les structures (arbitrairement grandes) sont plus coûteuses à transmettre que les références de classe (généralement une machine Word), de sorte que les classes pourraient être finalement plus rapides.

153
dsimcha

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

1) Vous utilisez des structures pour les performances stockées dans des tableaux. (voir aussi Quand est-ce que struct est la réponse? )

2) Vous en avez besoin dans le code qui transmet des données structurées vers/depuis C/C++.

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

  • Ils se comportent différemment des "objets normaux" ( types de référence ) sous affectation et lors de la transmission sous forme d'arguments, ce qui peut entraîner un comportement inattendu; Ceci est particulièrement dangereux si la personne qui regarde le code ne sait pas qu’elle a affaire à une structure.
  • Ils ne peuvent pas être hérités.
  • Passer des structures en tant qu'arguments coûte plus cher que les classes.
142
ILoveFortran

Utilisez une structure lorsque vous voulez une sémantique de valeur par opposition à une sémantique de référence.

Modifier

Je ne sais pas trop pourquoi les gens votent contre cela, mais c'est un argument valable. Il a été présenté avant que l'opérateur ne clarifie sa question et qu'il s'agisse de la raison fondamentale la plus fondamentale d'une struct.

Si vous avez besoin d'une sémantique de référence, vous avez besoin d'une classe et non d'une structure.

84
JoshBerke

En plus de la réponse "c’est une valeur", un scénario spécifique pour l’utilisation des structures est celui où vous savez que vous avez un ensemble de données qui cause des problèmes de garbage collection et vous avez beaucoup d'objets. Par exemple, une grande liste/tableau d'instances de personne. La métaphore naturelle ici est une classe, mais si vous avez un grand nombre d'instances Personne de longue durée, elles peuvent encrasser GEN-2 et provoquer des blocages de GC. Si le scénario le justifie, une approche potentielle consiste à utiliser un tableau (et non une liste) de Person structs, c'est-à-dire Person[]. Maintenant, au lieu d’avoir des millions d’objets dans GEN-2, vous n’avez qu’un seul bloc sur le LOH (je n’assume aucune chaîne, etc., c’est-à-dire une valeur pure sans références). Cela a très peu d'impact sur les GC.

Travailler avec ces données est difficile, car les données sont probablement trop grandes pour une structure, et vous ne voulez pas copier les grosses valeurs en permanence. Cependant, y accéder directement dans un tableau ne copie pas la structure: elle 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 ne devrait être tenté que si vous savez ce que vous faites et pourquoi. Le défaut ici serait une classe.

58
Marc Gravell

De la spécification du langage C # :

1.7 Structs

Comme les classes, les structures sont des structures de données pouvant contenir des membres de données et des membres de fonctions, mais contrairement aux classes, les structures sont des types valeur et ne nécessitent pas d'allocation de tas. Une variable de type struct stocke directement les données de la structure, tandis qu'une variable de type classe stocke une référence à un objet alloué de manière dynamique. Les types de structure ne prennent pas en charge l'héritage spécifié par l'utilisateur, et tous les types de structure héritent implicitement de l'objet type.

Les structures sont particulièrement utiles pour les petites structures de données ayant une sémantique de valeur. Les nombres complexes, les points dans un système de coordonnées ou les paires clé-valeur dans un dictionnaire sont tous de bons exemples de structures. L'utilisation de structures plutôt que de classes pour de petites structures de données peut faire une grande différence dans le nombre d'allocations de mémoire effectuées par une application. Par exemple, le programme suivant crée et initialise un tableau de 100 points. Avec Point implémenté en tant que classe, 101 objets distincts sont instanciés, un pour le tableau et un pour les 100 éléments.

class Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

class Test
{
   static void Main() {
      Point[] points = new Point[100];
      for (int i = 0; i < 100; i++) points[i] = new Point(i, i);
   }
}

Une alternative consiste à faire de Point une structure.

struct Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

Désormais, un seul objet est instancié - celui du tableau - et les instances de point sont stockées en ligne dans le tableau.

Les constructeurs de structures sont appelés avec l'opérateur new, mais cela n'implique pas que la mémoire est allouée. Au lieu d'allouer dynamiquement un objet et de lui renvoyer une référence, un constructeur de structure renvoie simplement la valeur de structure elle-même (généralement dans un emplacement temporaire de la pile), et cette valeur est ensuite copiée si nécessaire.

Avec les classes, il est possible que deux variables fassent référence au même objet et donc que des opérations sur une variable affectent l'objet référencé par l'autre variable. Avec les structures, les variables ont chacune leur propre copie des données et il est impossible pour les opérations sur l’une d’affecter l’autre. Par exemple, la sortie produite par le fragment de code suivant dépend de si Point est une classe ou une structure.

Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);

Si Point est une classe, la sortie est 20 car a et b font référence au même objet. Si Point est une structure, le résultat est 10 car l'affectation de a à b crée une copie de la valeur et cette copie n'est pas affectée par l'affectation ultérieure à a.x.

L'exemple précédent met en évidence deux des limitations des structures. Tout d'abord, la copie d'une structure entière est généralement moins efficace que la copie d'une référence d'objet. Par conséquent, le transfert d'affectation et de paramètre de valeur peut être plus coûteux avec des structures que avec des types de référence. Deuxièmement, à l'exception des paramètres ref et out, il n'est pas possible de créer des références à des structures, ce qui exclut leur utilisation dans un certain nombre de situations.

40
bUKaneer

Les structures sont bonnes pour la représentation atomique de données, lesdites données pouvant être copiées plusieurs fois par le code. Cloner un objet est généralement plus coûteux que de copier une structure, car cela implique d'allouer de la mémoire, d'exécuter le constructeur et de procéder à un désallocation/ramassage des ordures lorsque cela est fait.

34
Franci Penov

Voici une règle de base.

  • Si tous les champs membres sont des types valeur, créez un struct.

  • Si l'un des champs de membre est un type de référence, créez un classe. En effet, le champ de type de référence nécessitera de toute façon l’allocation de tas.

exemples

public struct MyPoint 
{
    public int X; // Value Type
    public int Y; // Value Type
}

public class MyPointWithName 
{
    public int X; // Value Type
    public int Y; // Value Type
    public string Name; // Reference Type
}
26
Usman Zafar

Tout d'abord: scénarios d'interopérabilité ou lorsque vous devez spécifier la disposition de la mémoire

Deuxièmement: quand les données ont presque la même taille qu'un pointeur de référence.

19
BC.

Vous devez utiliser une "structure" dans les cas où vous souhaitez spécifier explicitement la structure de la mémoire à l'aide de StructLayoutAttribute - généralement pour PInvoke.

Edit: Comment souligne que vous pouvez utiliser class ou struct avec StructLayoutAttribute, ce qui est certainement vrai. En pratique, vous utiliseriez généralement une structure - elle est allouée sur la pile par rapport au tas, ce qui est logique si vous ne faites que passer un argument à un appel de méthode non managé.

17
Maurice Flanagan

J'utilise des structures pour compresser ou décompresser tout type de format de communication binaire. Cela inclut la lecture ou l'écriture sur disque, les listes de sommets DirectX, les protocoles réseau ou le traitement de données chiffrées/compressées.

Les trois directives que vous avez énumérées ne m'ont pas été utiles dans ce contexte. Lorsque je dois écrire quatre cents octets d'éléments dans un ordre particulier, je vais définir une structure de quatre cents octets, et je vais la remplir avec toutes les valeurs non liées qu'elle est censée avoir, et je vais pour le mettre en place de la manière la plus logique à l’époque. (D'accord, quatre cents octets seraient assez étranges - mais à l'époque où j'écrivais des fichiers Excel, je travaillais avec des structures pouvant aller jusqu'à quarante octets, car c'est la taille de certains enregistrements BIFF.)

16
mjfgates

À l'exception des types de valeurs utilisés directement par le runtime et par d'autres à des fins d'Invoke, vous ne devez utiliser des types de valeur que dans 2 scénarios.

  1. Lorsque vous avez besoin de la sémantique de la copie.
  2. Lorsque vous avez besoin d’une initialisation automatique, normalement dans des tableaux de ces types.
15
leppie

.NET prend en charge value types et reference types (en Java, vous ne pouvez définir que des types de référence). Les instances de reference types sont allouées dans le segment de mémoire géré et sont collectées à la poubelle lorsqu'il n'y a aucune référence en attente. Les instances de value types, en revanche, sont allouées dans la stack et, par conséquent, la mémoire allouée est récupérée dès la fin de leur portée. Et bien sûr, value types est passé par valeur et reference types par référence. Tous les types de données primitifs C #, à l'exception de System.String, sont des types de valeur.

Quand utiliser struct over class,

En C #, structs sont value types, les classes sont reference types. Vous pouvez créer des types de valeur, en C #, à l'aide du mot clé enum et du mot clé struct. L'utilisation d'un value type au lieu d'un reference type réduit le nombre d'objets sur le segment de mémoire géré, ce qui entraîne une charge moindre sur le garbage collector (GC), des cycles de GC moins fréquents et, par conséquent, de meilleures performances. Cependant, value types a aussi ses inconvénients. Passer un gros struct est certainement plus coûteux que de passer une référence, c'est un problème évident. L'autre problème est la surcharge associée à boxing/unboxing. Si vous vous demandez ce que boxing/unboxing signifie, suivez ces liens pour une bonne explication sur boxing et unboxing. En dehors des performances, il est parfois nécessaire d'avoir des types dont la sémantique est une valeur, ce qui est très difficile (ou moche) à mettre en œuvre si reference types est tout ce que vous avez. Vous ne devez utiliser que value types, lorsque vous avez besoin d'une sémantique de copie ou d'une initialisation automatique, normalement dans arrays de ces types.

14
Sujit

Nah - Je ne suis pas entièrement d'accord avec les règles. Ce sont de bonnes directives à prendre en compte en matière de performances et de normalisation, mais pas à la lumière des possibilités.

Comme vous pouvez le constater dans les réponses, il existe de nombreuses manières créatives de les utiliser. Donc, ces directives doivent être les mêmes, toujours dans un souci de performance et d’efficacité.

Dans ce cas, j'utilise des classes pour représenter des objets du monde réel dans leur forme plus large, j'utilise des structures pour représenter des objets plus petits ayant des utilisations plus précises. La façon dont vous l'avez dit, "un tout plus cohérent". Le mot clé étant cohérent. Les classes seront des éléments plus orientés objet, tandis que les structures peuvent avoir certaines de ces caractéristiques, mais à une plus petite échelle. OMI.

Je les utilise beaucoup dans les balises Treeview et Listview, qui permettent d'accéder très rapidement aux attributs statiques courants. J'ai toujours eu du mal à obtenir cette information d'une autre manière. Par exemple, dans mes applications de base de données, j'utilise une arborescence de vues où j'ai des tableaux, des SP, des fonctions ou tout autre objet. Je crée et remplis ma structure, la mets dans la balise, la retire, récupère les données de la sélection, etc. Je ne ferais pas ça avec un cours!

J'essaie de les garder petits, de les utiliser dans des situations d'instance unique et de les empêcher de changer. Il est prudent de prendre conscience de la mémoire, de l'allocation et des performances. Et les tests sont si nécessaires.

10
SnapJag

Un struct est un type de valeur. Si vous affectez une structure à une nouvelle variable, celle-ci contiendra une copie de l'original.

public struct IntStruct {
    public int Value {get; set;}
}

L'exécution des résultats suivants dans 5 instances de la structure stockée en mémoire:

var struct1 = new IntStruct() { Value = 0 }; // original
var struct2 = struct1;  // A copy is made
var struct3 = struct2;  // A copy is made
var struct4 = struct3;  // A copy is made
var struct5 = struct4;  // A copy is made

// NOTE: A "copy" will occur when you pass a struct into a method parameter.
// To avoid the "copy", use the ref keyword.

// Although structs are designed to use less system resources
// than classes.  If used incorrectly, they could use significantly more.

Un classe est un type de référence. Lorsque vous affectez une classe à une nouvelle variable, celle-ci contient une référence à l'objet de classe d'origine.

public class IntClass {
    public int Value {get; set;}
}

L'exécution des résultats suivants a pour résultat ne seule instance de l'objet de classe en mémoire.

var class1 = new IntClass() { Value = 0 };
var class2 = class1;  // A reference is made to class1
var class3 = class2;  // A reference is made to class1
var class4 = class3;  // A reference is made to class1
var class5 = class4;  // A reference is made to class1  

Struct s peut augmenter le risque d'erreur de code. Si un objet de valeur est traité comme un objet de référence modifiable, un développeur peut être surpris lorsque des modifications apportées sont perdues de manière inattendue.

var struct1 = new IntStruct() { Value = 0 };
var struct2 = struct1;
struct2.Value = 1;
// At this point, a developer may be surprised when 
// struct1.Value is 0 and not 1
10
Jason Williams

Les types de structure en C # ou dans d'autres langages .net sont généralement utilisés pour contenir des éléments qui doivent se comporter comme des groupes de valeurs de taille fixe. Un aspect utile des types de structure est que les champs d'une instance de type structure peuvent être modifiés en modifiant l'emplacement de stockage dans lequel elle se trouve, et de manière non différente. Il est possible de coder une structure de telle sorte que le seul moyen de muter un champ quelconque consiste à construire une nouvelle instance entière, puis à utiliser une affectation de structure pour muter tous les champs de la cible en les écrasant avec les valeurs de la nouvelle instance. à moins qu'une structure ne fournisse aucun moyen de créer une instance dans laquelle ses champs ont des valeurs autres que celles par défaut, tous ses champs seront mutables si et si la structure elle-même est stockée dans un emplacement mutable.

Notez qu'il est possible de concevoir un type de structure de sorte qu'il se comporte essentiellement comme un type de classe, si la structure contient un champ de type classe privé et redirige ses propres membres vers ceux de l'objet de classe encapsulé. Par exemple, un PersonCollection peut offrir les propriétés SortedByName et SortedById, qui contiennent tous deux une référence "immuable" à un PersonCollection (défini dans leur constructeur) et implémentent GetEnumerator en appelant soit creator.GetNameSortedEnumerator ou creator.GetIdSortedEnumerator. De telles structures se comporteraient presque comme une référence à un PersonCollection, sauf que leurs méthodes GetEnumerator seraient liées à des méthodes différentes dans le PersonCollection. On pourrait aussi avoir une structure encapsulant une partie d’un tableau (par exemple on pourrait définir une structure ArrayRange<T> qui contiendrait un T[] appelé Arr, un int Offset et un int Length, avec une propriété indexée qui, pour un index idx compris entre 0 et Length-1, accéderait à Arr[idx+Offset]). Malheureusement, si foo est une instance en lecture seule d'une telle structure, les versions actuelles du compilateur n'autorisent pas d'opérations telles que foo[3]+=4; car elles ne permettent pas de déterminer si de telles opérations tentent d'écrire dans les champs de foo.

Il est également possible de concevoir une structure de telle sorte qu’elle se comporte comme un type de valeur contenant une collection de taille variable (qui semblera être copiée à chaque fois que la structure est présente), mais le seul moyen de le faire est d’assurer qu’aucun objet auquel le struct détient une référence sera jamais exposé à tout ce qui pourrait la transformer. Par exemple, on pourrait avoir une structure de type tableau qui contient un tableau privé et dont la méthode "put" indexée crée un nouveau tableau dont le contenu est similaire à celui de l'original, à l'exception d'un élément modifié. Malheureusement, il peut être assez difficile de rendre ces structures performantes. Parfois, la sémantique des structures peut être pratique (par exemple, être capable de passer une collection semblable à un tableau à une routine, l'appelant et l'appelé sachant que le code extérieur ne modifiera pas la collection, peut être mieux que d'exiger à la fois l'appelant et appelé à copier de manière défensive les données qui leur sont fournies), l’exigence selon laquelle les références de classe pointent vers des objets qui ne seront jamais mutés est souvent une contrainte assez sévère.

10
supercat

J'ai fait un petit test avec BenchmarkDotNet afin de mieux comprendre les avantages de "struct" en chiffres. Je teste en boucle à travers tableau (ou liste) de structs (ou classes). La création de ces tableaux ou de ces listes sort du cadre de la référence - il est clair que "la classe" est plus lourde, utilisera plus de mémoire et impliquera GC.

La conclusion est donc: soyez prudent avec LINQ et les structures cachées boxing/unboxing et utilisez des structures pour la microoptimisation restent strictement avec des tableaux.

P.S. Un autre point de repère sur le passage de la structure/classe à travers la pile d’appels est là https://stackoverflow.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;
        }
    }
10
Roman Pokrovskij

Une classe est un type de référence. Lorsqu'un objet de la classe est créé, la variable à laquelle l'objet est affecté ne contient qu'une référence à cette mémoire. Lorsque la référence à l'objet est affectée à une nouvelle variable, la nouvelle variable fait référence à l'objet d'origine. Les modifications apportées via une variable sont reflétées dans l'autre variable car elles font toutes deux référence aux mêmes données. Un struct est un type de valeur. Lorsqu'une structure est créée, la variable à laquelle la structure est affectée contient les données réelles de la structure. Lorsque la structure est assignée à une nouvelle variable, elle est copiée. La nouvelle variable et la variable d'origine contiennent donc deux copies distinctes des mêmes données. Les modifications apportées à une copie n'affectent pas l'autre copie. En général, les classes permettent de modéliser un comportement plus complexe ou les données destinées à être modifiées après la création d'un objet de classe. Les structures conviennent mieux aux petites structures de données contenant principalement des données qu'il n'est pas prévu de modifier après la création de la structure.

Classes et structures (Guide de programmation C #)

8
J_hajian_nzd

Ma règle est

1, toujours utiliser la classe;

2, S'il y a un problème de performances, j'essaie de changer une classe en struct en fonction des règles mentionnées par @IAbstract, puis je teste si ces modifications peuvent améliorer les performances.

8
rockXrock

Je pense qu'une bonne première approximation est "jamais".

Je pense qu'une bonne seconde approximation est "jamais".

Si vous êtes désespéré pour perf, considérez-les, mais alors toujours mesurer.

5
Brian

Je m'occupais juste de Windows Communication Foundation [WCF] Named Pipe et j'ai remarqué qu'il était logique d'utiliser Structs afin de s'assurer que l'échange de données est de type de valeur au lieu de type de référence.

5
N_E

La structure C # est une alternative légère à une classe. Cela peut faire presque la même chose qu'une classe, mais c'est moins "cher" d'utiliser une structure plutôt qu'une classe. La raison en est un peu technique, mais pour résumer, de nouvelles instances d'une classe sont placées sur le tas, où des structures nouvellement instanciées sont placées sur la pile. De plus, vous ne traitez pas de références à des structures, comme avec des classes, mais vous travaillez directement avec l'instance de structure. Cela signifie également que lorsque vous passez une structure à une fonction, c'est par valeur plutôt que par référence. Vous trouverez plus d'informations à ce sujet dans le chapitre relatif aux paramètres de fonction.

Donc, vous devriez utiliser des structures lorsque vous souhaitez représenter des structures de données plus simples, et surtout si vous savez que vous allez en instancier beaucoup. Il existe de nombreux exemples dans le cadre .NET, où Microsoft a utilisé des structures au lieu de classes, par exemple les structures Point, Rectangle et Color.

4
Saeed Dini

Brièvement, utilisez struct si:

1- vos propriétés d'objet/champs n'ont pas besoin d'être changé. Je veux dire que vous voulez juste leur donner une valeur initiale et ensuite les lire.

2- Les propriétés et les champs de votre objet sont du type valeur et ils ne sont pas si grands.

Si tel est le cas, vous pouvez tirer parti des structures pour de meilleures performances et une allocation de mémoire optimisée, car elles utilisent uniquement des piles plutôt que des piles et des tas (en classes).

3
akazemis

Struct peut être utilisé pour améliorer les performances de la récupération de place. Bien que vous n’ayez généralement pas à vous soucier des performances du GC, il existe des scénarios dans lesquels cela peut être mortel. Comme les grands caches dans les applications à faible temps de latence. Voir ce post pour un exemple:

http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/

3
Rabbit

Les types de structure ou de valeur peuvent être utilisés dans les scénarios suivants:

  1. Si vous voulez empêcher que l'objet soit collecté par garbage collection.
  2. S'il s'agit d'un type simple et qu'aucune fonction membre ne modifie ses champs d'instance
  3. S'il n'est pas nécessaire de dériver d'autres types ou d'être dérivé à d'autres types.

Vous pouvez en savoir plus sur les types de valeur et les valeurs types ici sur ce lien

3
Vikram

MYTHE N ° 1: LES STRUCTS SONT DES CLASSES LEGERES

Ce mythe se présente sous différentes formes. Certaines personnes pensent que les types de valeur ne peuvent ou ne doivent pas avoir de méthodes ou d’autres comportements importants. Ils doivent être utilisés comme de simples types de transfert de données, avec uniquement des champs publics ou des propriétés simples. Le type DateTime est un bon contre-exemple à cela: il est logique que ce soit un type de valeur, en tant qu’unité fondamentale comme un nombre ou un caractère, et il est également judicieux de pouvoir effectuer des calculs basés sur Sa valeur. En regardant les choses de l’autre côté, les types de transfert de données doivent souvent être des types de référence. La décision doit être basée sur la valeur souhaitée ou sur la sémantique du type de référence, et non sur la simplicité du type. D'autres personnes pensent que les types de valeur sont "plus légers" que les types de référence en termes de performances. La vérité est que, dans certains cas, les types de valeur sont plus performants: ils ne nécessitent pas de récupération de place, sauf s’ils sont encadrés, ne nécessitent pas d’identité de type et ne nécessitent pas de déréférencement, par exemple. D’une autre manière, les types de référence sont plus performants: la transmission de paramètres, l’affectation de valeurs à des variables, le renvoi de valeurs et des opérations similaires ne nécessitent que 4 ou 8 octets pour être copiés (selon que vous exécutiez le CLR 32 bits ou 64 bits). ) plutôt que de copier toutes les données. Imaginez si ArrayList était en quelque sorte un type de valeur "pure" et que transmettre une expression ArrayList à une méthode impliquait de copier toutes ses données! Dans presque tous les cas, la performance n’est pas vraiment déterminée par ce type de décision. Les goulots d'étranglement ne se situent presque jamais à l'endroit où vous pensez les trouver et, avant de prendre une décision de conception basée sur les performances, vous devez mesurer les différentes options. Il convient de noter que la combinaison des deux croyances ne fonctionne pas non plus. Peu importe le nombre de méthodes d’un type (qu’il s’agisse d’une classe ou d’une structure), la mémoire utilisée par instance n’est pas affectée. (Il y a un coût en termes de mémoire utilisée pour le code lui-même, mais cela n’est encouru qu’une seule fois plutôt que pour chaque instance.)

MYTHE n ° 2: LES TYPES DE RÉFÉRENCE VIVENT À L'HEURE; TYPES DE VALEUR VIVANT SUR LA PILE

Celui-ci est souvent causé par la paresse de la personne qui le répète. La première partie est correcte: une instance d'un type de référence est toujours créée sur le tas. C’est la deuxième partie qui pose problème. Comme je l'ai déjà noté, la valeur d'une variable est conservée partout où elle est déclarée. Par conséquent, si vous avez une classe avec une variable d'instance de type int, la valeur de cette variable pour un objet donné sera toujours à l'emplacement où le reste des données de l'objet est ... sur le tas. Seules les variables locales (variables déclarées dans les méthodes) et les paramètres de méthode résident sur la pile. Dans C # 2 et les versions ultérieures, même certaines variables locales ne vivent pas vraiment dans la pile, comme vous le verrez lorsque nous examinerons les méthodes anonymes au chapitre 5. CES CONCEPTS SONT-ILS PERTINENTS? On peut soutenir que si vous écrivez du code géré, vous devriez laisser le runtime s’inquiéter de la meilleure utilisation de la mémoire. En effet, la spécification de la langue ne donne aucune garantie sur ce qui vit où; un futur moteur d'exécution pourra peut-être créer des objets sur la pile s'il sait qu'il peut s'en tirer, ou le compilateur C # pourrait générer un code qui n'utilise pratiquement pas la pile. Le mythe suivant est généralement juste un problème de terminologie.

MYTHE 3: LES OBJETS SONT PASSÉS PAR RÉFÉRENCE EN C # PAR DÉFAUT

C'est probablement le mythe le plus répandu. Encore une fois, les personnes qui font cette affirmation savent souvent (mais pas toujours) le comportement réel de C #, mais elles ne savent pas ce que signifie "passer par référence". Malheureusement, c'est déroutant pour les personnes qui savent ce que cela signifie. La définition formelle de passe par référence est relativement compliquée, impliquant des valeurs L et une terminologie informatique similaire, mais l'important est que si vous transmettez une variable par référence, la méthode que vous appelez peut changer la valeur de la variable de l'appelant. en changeant sa valeur de paramètre. Rappelez-vous que la valeur d'une variable de type référence est la référence, pas l'objet lui-même. Vous pouvez modifier le contenu de l'objet auquel un paramètre fait référence sans que le paramètre lui-même soit passé par référence. Par exemple, la méthode suivante modifie le contenu de l'objet StringBuilder en question, mais l'expression de l'appelant fera toujours référence au même objet qu'auparavant:

void AppendHello(StringBuilder builder)
{
    builder.Append("hello");
}

Lorsque cette méthode est appelée, la valeur du paramètre (une référence à un StringBuilder) est transmise par valeur. Si vous deviez modifier la valeur de la variable de générateur dans la méthode (par exemple, avec l’énoncé constructeur = null), cette modification ne serait pas vue par l’appelant, contrairement au mythe. Il est intéressant de noter que non seulement le bit "par référence" du mythe est inexact, mais il en est de même pour le bit "objets sont passés". Les objets eux-mêmes ne sont jamais transmis, ni par référence ni par valeur. Lorsqu'un type de référence est impliqué, la variable est passée par référence ou la valeur de l'argument (la référence) est passée par valeur. Mis à part toute autre chose, cela répond à la question de savoir ce qui se passe lorsque null est utilisé comme argument par valeur. Si des objets étaient échangés, cela causerait des problèmes, puisqu’il n’y aurait pas d’objet à transmettre! Au lieu de cela, la référence null est passée par valeur de la même manière que toute autre référence. Si cette explication rapide vous a laissé perplexe, vous voudrez peut-être consulter mon article "Passage de paramètres en C #" ( http://mng.bz/otVt ), qui est beaucoup plus détaillé. . Ces mythes ne sont pas les seuls autour. La boxe et le déballage entrent pour une bonne part de malentendus, ce que je vais essayer d’éclaircir.

Référence: C # in Depth 3rd Edition par Jon Skeet

3
habib