Quand dois-je utiliser une structure plutôt qu'une classe en C #?

1391

Quand faut-il utiliser struct et pas class en C #? Mon modèle conceptuel est que les structures sont utilisées à des moments où l'élément n'est qu'une collection de types de valeur . Une façon de les logiquement tous tenir ensemble dans un ensemble cohérent.

Je suis tombé sur ces règles ici :

  • Une structure doit 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 sa création.

Ces règles fonctionnent-elles? Que signifie une structure sémantiquement?

Alex Baranosky
la source
248
System.Drawing.Rectangleviole ces trois règles.
ChrisW
4
il y a pas mal de jeux commerciaux écrits en C #, le fait est qu'ils sont utilisés pour du code optimisé
BlackTigerX
25
Les structures offrent de meilleures performances lorsque vous disposez de petites collections de types de valeur que vous souhaitez regrouper. Cela se produit tout le temps dans la programmation de jeux, par exemple, un sommet dans un modèle 3D aura une position, une coordonnée de texture et une normale, il sera également généralement immuable. Un modèle unique peut avoir quelques milliers de sommets, ou il peut en avoir une douzaine, mais les structures fournissent globalement moins de frais généraux dans ce scénario d'utilisation. J'ai vérifié cela à travers ma propre conception de moteur.
Chris D.
4
@ChrisW Je vois, mais ces valeurs ne représentent-elles pas un rectangle, c'est-à-dire une valeur "unique"? Comme Vector3D ou Color, ce sont aussi plusieurs valeurs à l'intérieur, mais je pense qu'elles représentent des valeurs uniques?
Marson Mao

Réponses:

604

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é un apprentissage supplémentaire 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 du type sont petites et généralement de courte durée ou sont généralement intégrées dans d'autres objets.

Ne définissez une structure que si le type possède 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 n'aura pas à être emballé fréquemment.

Microsoft viole systématiquement ces règles

D'accord, n ° 2 et n ° 3 de toute façon. 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 car # 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 Entryet Enumeratorreprésentent des valeurs uniques.
  2. La vitesse
  3. Entryn'est jamais passé en tant que paramètre en dehors de la classe Dictionary. Une enquête plus approfondie montre que pour satisfaire l'implémentation de IEnumerable, Dictionary utilise la Enumeratorstructure qu'il copie chaque fois qu'un énumérateur est demandé ... est logique.
  4. Interne à la classe Dictionary. Enumeratorest public car Dictionary est énumérable et doit avoir une accessibilité égale à l'implémentation de l'interface IEnumerator - par exemple le getter IEnumerator.

Mise à jour - En outre, sachez que lorsqu'une structure implémente une interface - comme le fait l'énumérateur - et est convertie en ce type implémenté, la structure devient un type de référence et est déplacée vers le segment de mémoire. En 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 IEnumeratorest renvoyé.

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

  1. Rien dans les structures ci-dessus n'est déclaré readonly- non immuable
  2. La taille de ces structures pourrait être bien supérieure à 16 octets
  3. Entrya une durée de vie indéterminée (à partir de Add(), à Remove(), Clear()ou la collecte des ordures);

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

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é: 5 ms
Temps total de remplissage: 889 ms

Capacité : nombre d'éléments disponibles avant le redimensionnement du tableau interne.

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

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

Temps total à remplir : certes biaisé en raison de la journalisation et d'un OnResizeévénement que j'ai ajouté à la source; cependant, toujours impressionnant pour remplir 300k entiers tout en redimensionnant 15 fois pendant l'opération. Par simple curiosité, quel serait le temps total à remplir si je connaissais déjà la capacité? 13ms

Alors, maintenant, et si Entryc'était une classe? Ces temps ou mesures différeraient-ils vraiment autant?

Capacité: 312874
MemSize: 2660827 octets
Redimensionnement terminé: 26ms
Temps total de remplissage: 964ms

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

Ce qui se passe est, car Entryest une structure, elle 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. Afin de l'utiliser Entrycomme 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 en Entrytant que type de référence se trouve sur 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, 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 du tableau.

Certains compilateurs, tels que le compilateur C #, ne permettent pas aux structures d'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, sauf si cela enfreindrait la règle # 1
  3. La structure doit rester intacte pendant son utilisation, sauf si sa destruction est requise pour satisfaire à la règle n ° 1.

... que retenons-nous de cela : bref, 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).

IAbstract
la source
8
En ce qui concerne les règles de Microsoft, la règle relative à l'immuabilité semble être conçue pour décourager l'utilisation des types de valeur de telle sorte que leur comportement diffère de celui des types de référence, malgré le fait que la sémantique des valeurs mutables par morceaux peut être utile . Si le fait d'avoir un type mutable par morceaux faciliterait l'utilisation, et si les emplacements de stockage du type devaient être logiquement séparés les uns des autres, le type devrait être une structure "mutable".
supercat
23
Gardez à l'esprit que readonly! = Immuable.
Justin Morgan
2
Le fait que de nombreux types de Microsoft violent ces règles ne représente pas un problème avec ces types, mais indique plutôt que les règles ne devraient pas s'appliquer à tous les types de structure. Si une structure représente une seule entité [comme avec Decimalou DateTime], alors si elle ne respecte pas les trois autres règles, elle doit être remplacée par une classe. Si une structure contient une collection fixe de variables, dont chacune peut contenir n'importe quelle valeur qui serait valide pour son type [par exemple Rectangle], alors elle doit respecter des règles différentes , dont certaines sont contraires à celles des structures "à valeur unique" .
supercat
4
@IAbstract: Certaines personnes justifieraient le Dictionarytype d'entrée sur la base qu'il s'agit uniquement d'un type interne, les performances ont été considérées comme plus importantes que la sémantique ou une autre excuse. Mon point est qu'un type comme Rectangledevrait avoir son contenu exposé en tant que champs modifiables individuellement non pas "parce que" les avantages de performance l'emportent sur les imperfections sémantiques résultantes, mais parce que le type représente sémantiquement un ensemble fixe de valeurs indépendantes , et donc la structure mutable est à la fois plus performant et sémantiquement supérieur .
supercat
2
@supercat: Je suis d'accord ... et le but de ma réponse était que les «lignes directrices» sont assez faibles et que les structures doivent être utilisées en toute connaissance et compréhension des comportements. Voir ma réponse sur la structure mutable ici: stackoverflow.com/questions/8108920/…
IAbstract
155

Quand tu:

  1. pas besoin de polymorphisme,
  2. veulent une sémantique de valeur, et
  3. veulent éviter l'allocation de segments de mémoire et les frais généraux de récupération de place associés.

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 un mot machine), donc les classes pourraient finir par être plus rapides dans la pratique.

dsimcha
la source
1
Ce n'est qu'une "mise en garde". Devrait également envisager de «lever» des types de valeur et des cas tels que (Guid)null(il est normal de convertir un null en type de référence), entre autres.
1
plus cher qu'en C / C ++? en C ++, la méthode recommandée est de passer des objets par valeur
Ion Todirel
@IonTodirel N'était-ce pas pour des raisons de sécurité de la mémoire, plutôt que pour des performances? C'est toujours un compromis, mais passer 32 B par pile est toujours (TM) plus lent que passer une référence 4 B par registre. Cependant , notez également que l'utilisation de "valeur / référence" est un peu différente en C # et C ++ - lorsque vous passez une référence à un objet, vous passez toujours par valeur, même si vous passez une référence (vous ' en passant la valeur de la référence, pas une référence à la référence, fondamentalement). Ce n'est pas de la sémantique de la valeur , mais c'est techniquement "passe-par-valeur".
Luaan
@Luaan Copying n'est qu'un aspect des coûts. L'indirection supplémentaire due au pointeur / référence coûte également par accès. Dans certains cas, la structure peut même être déplacée et n'a donc même pas besoin d'être copiée.
Onur
@Onur c'est intéressant. Comment "bouger" sans copier? Je pensais que l'instruction asm "mov" ne "bougeait" pas. Il copie.
Ailier Sendon
148

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

1) Vous utilisez des structures pour les performances lorsqu'elles sont stockées dans des tableaux. (voir aussi Quand les structures sont-elles la réponse? )

2) Vous en avez besoin dans le code en passant des données structurées vers / depuis 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 affectation et lors du passage en arguments, ce qui peut conduire à un comportement inattendu; cela est particulièrement dangereux si la personne qui regarde le code ne sait pas qu'il s'agit d'un struct.
  • Ils ne peuvent pas être hérités.
  • Passer des structures comme arguments est plus cher que les classes.
ILoveFortran
la source
4
+1 Oui, je suis entièrement d'accord sur le n ° 1 (c'est un énorme avantage lorsqu'il s'agit de choses comme des images, etc.) et pour avoir souligné qu'ils sont différents des "objets normaux" et qu'il existe un moyen connu de le savoir, sauf par les connaissances existantes ou en examinant le type lui-même. En outre, vous ne pouvez pas convertir une valeur nulle en un type de structure :-) C'est en fait un cas où je souhaite presque qu'il y ait un `` hongrois '' pour les types de valeur non Core ou un mot clé `` struct '' obligatoire sur le site de déclaration de variable .
@pst: Il est vrai que l'on doit savoir quelque chose est structde savoir comment il se comportera, mais si quelque chose est structavec des champs exposés, c'est tout ce que l'on doit savoir. Si un objet expose une propriété d'un type de structure de champ exposé, et si le code lit cette structure dans une variable et la modifie, on peut prédire en toute sécurité qu'une telle action n'affectera pas l'objet dont la propriété a été lue à moins que ou jusqu'à ce que la structure soit écrite retour. En revanche, si la propriété était un type de classe mutable, la lire et la modifier pourrait mettre à jour l'objet sous-jacent comme prévu, mais ...
supercat
... cela pourrait aussi finir par ne rien changer, ou cela pourrait changer ou corrompre des objets que l'on n'avait pas l'intention de changer. Avoir du code dont la sémantique dit "changer cette variable tout ce que vous voulez; les changements ne feront rien tant que vous ne les aurez pas explicitement stockés quelque part" semble plus clair que d'avoir du code qui dit "Vous obtenez une référence à un objet, qui pourrait être partagé avec n'importe quel nombre d'autres références, ou peuvent ne pas être partagées du tout; vous devrez déterminer qui d'autre pourrait avoir des références à cet objet pour savoir ce qui se passera si vous le modifiez. "
supercat
Repérez avec # 1. Une liste pleine de structures peut compresser des données beaucoup plus pertinentes dans des caches L1 / L2 qu'une liste pleine de références d'objets (pour la structure de bonne taille).
Matt Stephenson du
2
L'héritage est rarement le bon outil pour le travail, et raisonner trop sur les performances sans profilage est une mauvaise idée. Tout d'abord, les structures peuvent être passées par référence. Deuxièmement, le passage par référence ou par valeur est rarement un problème de performance significatif. Enfin, vous ne tenez pas compte de l'allocation de tas supplémentaire et de la récupération de place qui doivent avoir lieu pour une classe. Personnellement, je préfère considérer les structures comme des données anciennes et les classes comme des choses qui font des choses (objets), bien que vous puissiez également définir des méthodes sur les structures.
weberc2
88

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

Éditer

Je ne sais pas pourquoi les gens votent en faveur de cela, mais c'est un point valable, et cela a été fait avant que l'op clarifie sa question, et c'est la raison fondamentale la plus fondamentale d'une structure.

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

JoshBerke
la source
13
Tout le monde sait ça. On dirait qu'il cherche plus qu'une réponse "struct est un type de valeur".
TheSmurf
21
C'est le cas le plus élémentaire et devrait être indiqué pour toute personne qui lit ce post et ne le sait pas.
JoshBerke
3
Non pas que cette réponse ne soit pas vraie; c'est évidemment le cas. Ce n'est pas vraiment le point.
TheSmurf
55
@Josh: Pour ceux qui ne le savent pas déjà, dire simplement que c'est une réponse insuffisante, car il est fort probable qu'ils ne savent pas non plus ce que cela signifie.
TheSmurf
1
Je viens de rétrograder cela parce que je pense que l'une des autres réponses devrait être en haut - toute réponse qui dit "Pour l'interopérabilité avec du code non géré, sinon éviter".
Daniel Earwicker
59

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 que vous avez beaucoup d'objets. Par exemple, une grande liste / tableau d'instances Personne. La métaphore naturelle ici est une classe, mais si vous avez un grand nombre d'instances de personne à longue durée de vie, elles peuvent finir par obstruer GEN-2 et provoquer des blocages GC. Si le scénario le justifie, une approche potentielle ici consiste à utiliser un tableau (pas une liste) de structures Person , c'est-à-dire Person[]. Maintenant, au lieu d'avoir des millions d'objets dans GEN-2, vous avez un seul morceau sur la LOH (je suppose qu'il n'y a pas de chaînes, etc. ici - c'est-à-dire une valeur pure 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 trop grandes pour une structure, et vous ne voulez pas copier les valeurs de graisse tout le temps. 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, cela 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. La valeur par défaut ici serait une classe.

Marc Gravell
la source
+1 Réponse intéressante. Seriez-vous prêt à partager des anecdotes du monde réel sur une telle approche utilisée?
Jordão
@Jordao sur mobile, mais cherchez sur google: + gravell + "assault by GC"
Marc Gravell
1
Merci beaucoup. Je l'ai trouvé ici .
Jordão
2
@MarcGravell Pourquoi avez-vous mentionné: utiliser un tableau (pas une liste) ? ListJe crois, utilise un Arrayarrière-plan. non ?
Royi Namir
4
@RoyiNamir J'étais également curieux à ce sujet, mais je pense que la réponse se trouve dans le deuxième paragraphe de la réponse de Marc. "Cependant, y accéder directement dans un tableau ne copie pas la structure - elle est en place (contrairement à un indexeur de liste, qui copie)."
user1323245
40

De la spécification du langage C # :

1.7 Structs

Comme les classes, les structures sont des structures de données qui peuvent contenir des membres de données et des membres de fonction, mais contrairement aux classes, les structures sont des types de valeur et ne nécessitent pas d'allocation de segment de mémoire. Une variable d'un type struct stocke directement les données de la structure, tandis qu'une variable d'un type class stocke une référence à un objet alloué dynamiquement. Les types de structures ne prennent pas en charge l'héritage spécifié par l'utilisateur, et tous les types de structures héritent implicitement de l'objet type.

Les structures sont particulièrement utiles pour les petites structures de données qui ont 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 les petites structures de données peut faire une grande différence dans le nombre d'allocations de mémoire qu'une application effectue. 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 est de 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 Point sont stockées en ligne dans le tableau.

Les constructeurs de structures sont invoqués avec le nouvel opérateur, 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 sur la pile), et cette valeur est ensuite copiée si nécessaire.

Avec les classes, il est possible que deux variables référencent le même objet et donc possible que les 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 n'est pas possible que les opérations sur l'une affectent l'autre. Par exemple, la sortie produite par le fragment de code suivant dépend 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, la sortie 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 suivante à ax

L'exemple précédent met en évidence deux des limitations des structures. Premièrement, la copie d'une structure entière est généralement moins efficace que la copie d'une référence d'objet, de sorte que l'attribution et le passage de paramètres de valeur peuvent être plus coûteux avec des structures qu'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.

Luke Baughan
la source
4
Bien que le fait que les références aux structures ne puissent pas être persistantes est parfois une limitation, c'est aussi une caractéristique très utile. L'une des principales faiblesses de .net est qu'il n'existe aucun moyen décent de transmettre en dehors du code une référence à un objet modifiable sans perdre à jamais le contrôle de cet objet. En revanche, on peut donner en toute sécurité une méthode externe refà une structure mutable et savoir que toutes les mutations que la méthode externe effectuera sur elle seront effectuées avant son retour. C'est dommage .net n'a aucun concept de paramètres éphémères et de valeurs de retour de fonction, car ...
supercat
4
... qui permettrait de réaliser la sémantique avantageuse des structures passées refavec des objets de classe. Essentiellement, les variables locales, les paramètres et les valeurs de retour de fonction peuvent être persistants (par défaut), renvoyables ou éphémères. Il serait interdit au code de copier des choses éphémères sur tout ce qui survivrait à la portée actuelle. Les choses retournables seraient comme des choses éphémères, sauf qu'elles pourraient être renvoyées par une fonction. La valeur de retour d'une fonction serait liée par les restrictions les plus strictes applicables à l'un de ses paramètres "renvoyables".
supercat
34

Les structures sont bonnes 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 coûteux que la copie d'une structure, car cela implique d'allouer de la mémoire, d'exécuter le constructeur et de désallouer / garbage collection lorsque vous en avez terminé.

Franci Penov
la source
4
Oui, mais les grandes structures peuvent être plus chères que les références de classe (lors du passage aux méthodes).
Alex
27

Voici une règle de base.

  • Si tous les champs membres sont des types de valeur, créez une structure .

  • Si un champ membre est un type de référence, créez une classe . Cela est dû au fait que le champ de type de référence aura quand même besoin de l'allocation de segment de mémoire.

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
}
Usman Zafar
la source
3
Les types de référence immuables comme stringsont sémantiquement équivalents aux valeurs, et le stockage d'une référence à un objet immuable dans un champ n'entraîne pas d'allocation de segment de mémoire. La différence entre une structure avec des champs publics exposés et un objet de classe avec des champs publics exposés est que, étant donné la séquence de code var q=p; p.X=4; q.X=5;, p.Xaura la valeur 4 s'il as'agit d'un type de structure et 5 s'il s'agit d'un type de classe. Si l'on souhaite pouvoir modifier commodément les membres du type, il faut sélectionner «classe» ou «struct» selon que l'on souhaite ou nonq affectent p.
supercat
Oui, je suis d'accord que la variable de référence sera sur la pile mais l'objet auquel elle fait référence existera sur le tas. Bien que les structures et les classes se comportent différemment lorsqu'elles sont affectées à une variable différente, mais je ne pense pas que ce soit un facteur décisif fort.
Usman Zafar
Les structures mutables et les classes mutables se comportent complètement différemment; si l'un a raison, l'autre aura très probablement tort. Je ne sais pas comment le comportement ne serait pas un facteur décisif pour déterminer s'il faut utiliser une structure ou une classe.
supercat
J'ai dit que ce n'était pas un facteur décisif fort parce que souvent, lorsque vous créez une classe ou une structure, vous ne savez pas comment il sera utilisé. Vous vous concentrez donc sur la façon dont les choses ont plus de sens du point de vue de la conception. Quoi qu'il en soit, je n'ai jamais vu à un seul endroit dans la bibliothèque .NET où une structure contient une variable de référence.
Usman Zafar
1
Le type de structure ArraySegment<T>encapsule un T[], qui est toujours un type de classe. Le type de structure KeyValuePair<TKey,TValue>est souvent utilisé avec des types de classe comme paramètres génériques.
supercat
19

Premièrement: 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 de toute façon.

AVANT JC.
la source
17

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

Edit: Comment souligne que vous pouvez utiliser une classe ou une structure avec StructLayoutAttribute et c'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 passez simplement un argument à un appel de méthode non managé.

Maurice Flanagan
la source
5
Le StructLayoutAttribute peut être appliqué à des structures ou à des classes, ce n'est donc pas une raison pour utiliser des structures.
Stephen Martin
Pourquoi cela a-t-il un sens si vous passez simplement un argument à un appel de méthode non managé?
David Klempfner
16

J'utilise des structures pour emballer ou déballer toute sorte 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 des données chiffrées / compressées.

Les trois lignes directrices que vous énumérez ne m'ont pas été utiles dans ce contexte. Quand j'ai besoin d'écrire quatre cents octets de choses dans un ordre particulier, je vais définir une structure de quatre cents octets, et je vais le remplir avec toutes les valeurs non liées qu'il est censé avoir, et je vais pour le configurer 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 pour gagner ma vie, je faisais face à des structures pouvant aller jusqu'à une quarantaine d'octets partout, car c'est la taille de certains enregistrements BIFF.)

mjfgates
la source
Ne pourriez-vous pas tout aussi facilement utiliser un type de référence pour cela?
David Klempfner
15

À l'exception des types de valeurs qui sont utilisés directement par le runtime et divers autres à des fins PInvoke, vous ne devez utiliser les types de valeurs que dans 2 scénarios.

  1. Lorsque vous avez besoin de copier la sémantique.
  2. Lorsque vous avez besoin d'une initialisation automatique, normalement dans des tableaux de ces types.
leppie
la source
# 2 semble faire partie de la raison de la prévalence des structures dans les classes de collecte .Net ..
IAbstract
Si la première chose que l'on ferait lors de la création d'un emplacement de stockage d'un type de classe serait de créer une nouvelle instance de ce type, de stocker une référence à cet emplacement et de ne jamais copier la référence nulle part ailleurs ni de l'écraser, puis un struct et la classe se comporterait de manière identique. Les structures ont un moyen standard pratique de copier tous les champs d'une instance à une autre et offrent généralement de meilleures performances dans les cas où l'on ne dupliquerait jamais une référence à une classe (à l'exception du thisparamètre éphémère utilisé pour appeler ses méthodes); les classes permettent de dupliquer des références.
supercat
13

.NET prend en charge value typeset reference types(en Java, vous ne pouvez définir que des types de référence). Les instances de reference typesget sont allouées dans le tas managé et sont récupérées quand il n'y a aucune référence en suspens à elles. Les instances de value types, d'autre part, sont allouées dans le stack, et donc la mémoire allouée est récupérée dès que leur portée se termine. Et bien sûr, value typespassez par la valeur, etreference 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 sur classe,

En C #, structssont value types, les classes sont reference types. Vous pouvez créer des types de valeur, en C #, à l'aide du enummot-clé et du structmot - clé. L'utilisation d'un value typeau lieu d'un reference typeentraînera moins d'objets sur le tas géré, ce qui se traduira par une charge moindre sur le garbage collector (GC), des cycles GC moins fréquents et par conséquent de meilleures performances. Cependant, ils value typesont aussi leurs inconvénients. Faire le tour d'un gros structest 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 boxing/unboxingque cela signifie, suivez ces liens pour une bonne explication boxingetunboxing. En dehors des performances, il y a des moments où vous avez simplement besoin que les types aient une sémantique de valeur, ce qui serait très difficile (ou moche) à implémenter sireference typesêtes tout ce que vous avez. Vous ne devez utiliser value typesque lorsque vous avez besoin de copier la sémantique ou que vous avez besoin d'une initialisation automatique, normalement dans arraysces types.

Sujit
la source
Copier de petites structures ou passer par la valeur est aussi bon marché que copier ou passer une référence de classe, ou passer les structures ref. Passer une structure de taille par refcoûte le même prix que passer une référence de classe par valeur. Copier une structure de taille ou passer par une valeur est moins cher que d'effectuer une copie défensive d'un objet de classe et de stocker ou de transmettre une référence à cela. Les grandes classes de temps sont meilleures que les structures pour stocker les valeurs sont (1) lorsque les classes sont immuables (afin d'éviter la copie défensive), et chaque instance qui est créée passera beaucoup, ou ...
supercat
... (2) lorsque, pour diverses raisons, une structure ne serait tout simplement pas utilisable [par exemple parce qu'il faut utiliser des références imbriquées pour quelque chose comme un arbre, ou parce qu'il faut du polymorphisme]. Notez que lorsque vous utilisez des types de valeur, il faut généralement exposer les champs directement en l'absence d'une raison particulière de ne pas le faire (alors qu'avec la plupart des types de classe, les champs doivent être enveloppés dans des propriétés). Beaucoup de soi-disant «maux» des types de valeurs mutables proviennent de l'encapsulation inutile de champs dans les propriétés (par exemple, alors que certains compilateurs permettraient d'appeler un configurateur de propriétés sur une structure en lecture seule, car cela pourrait parfois ...
supercat
... faites ce qu'il faut, tous les compilateurs rejetteraient correctement les tentatives de définition directe de champs sur de telles structures; le meilleur moyen de s'assurer que les compilateurs rejettent readOnlyStruct.someMember = 5;n'est pas de créer someMemberune propriété en lecture seule, mais plutôt d'en faire un champ.
supercat
12

Un struct est un type de valeur. Si vous affectez une structure à une nouvelle variable, la nouvelle variable 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.

Une classe est un type de référence. Lorsque vous affectez une classe à une nouvelle variable, la variable 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 dans une seule instance de l'objet 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  

Les structures peuvent augmenter la probabilité d'une erreur de code. Si un objet valeur est traité comme un objet de référence mutable, 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
Jason Williams
la source
12

J'ai fait un petit benchmark avec BenchmarkDotNet pour avoir une meilleure compréhension de l'avantage "struct" en nombre. Je teste en boucle un tableau (ou une liste) de structures (ou classes). La création de ces tableaux ou listes est hors de portée du benchmark - 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 l'utilisation de structures pour les microoptimisations reste strictement avec les tableaux.

PS Un autre point de référence sur le passage de struct / 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;
        }
    }
Roman Pokrovskij
la source
Avez-vous compris pourquoi les structures sont tellement plus lentes lorsqu'elles sont utilisées dans des listes et autres? Est-ce à cause de la boxe et du déballage cachés que vous avez mentionnés? Si oui, pourquoi cela se produit-il?
Marko Grdinic
L'accès à struct dans le tableau devrait être plus rapide, car aucun référencement supplémentaire n'est requis. Boxe / Unboxing est un cas pour linq.
Roman Pokrovskij
10

Les types de structure en C # ou dans d'autres langages .net sont généralement utilisés pour contenir des éléments qui devraient 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 est conservée, et en aucune autre manière. Il est possible de coder une structure de telle manière que la seule façon de muter un champ est de construire une toute nouvelle instance, puis d'utiliser une affectation de structure pour muter tous les champs de la cible en les écrasant avec les valeurs de la nouvelle instance, mais à moins qu'un struct ne fournisse aucun moyen de créer une instance où ses champs ont des valeurs non par défaut, tous ses champs seront mutables si et si le struct lui-même est stocké dans un emplacement mutable.

Notez qu'il est possible de concevoir un type de structure afin 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 celui de l'objet classe encapsulé. Par exemple, un PersonCollectionpourrait offrir des propriétés SortedByNameet SortedById, qui détiennent toutes deux une référence "immuable" à un PersonCollection(défini dans leur constructeur) et l'implémenter GetEnumeratoren appelant soit creator.GetNameSortedEnumeratorou creator.GetIdSortedEnumerator. De telles structures se comporteraient comme une référence à a PersonCollection, sauf que leurs GetEnumeratorméthodes seraient liées à différentes méthodes dans le PersonCollection. On pourrait également avoir une structure envelopper une partie d'un tableau (par exemple, on pourrait définir une ArrayRange<T>structure qui contiendrait un T[]appelé Arr, un intOffset et un intLength, Avec une propriété indexée qui, pour un index idxdans la plage 0 à Length-1, aurait accès Arr[idx+Offset]). Malheureusement, s'il foos'agit d'une instance en lecture seule d'une telle structure, les versions actuelles du compilateur n'autoriseront pas les opérations comme, foo[3]+=4;car elles n'ont aucun moyen de déterminer si ces opérations tenteront d'écrire dans les champs de foo.

Il est également possible de concevoir une structure pour se comporter comme un type de valeur similaire qui contient une collection de taille variable (qui semblera être copiée chaque fois que la structure est), mais la seule façon de faire fonctionner cela est de s'assurer qu'aucun objet auquel le struct détient une référence sera jamais exposée à tout ce qui pourrait la muter. 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 quelque peu difficile de faire fonctionner efficacement ces structures. Bien qu'il y ait des moments où la sémantique des structures peut être pratique (par exemple, pouvoir passer une collection de type tableau à une routine, l'appelant et l'appelé sachant tous les deux que le code extérieur ne modifiera pas la collection,

supercat
la source
10

Non - je ne suis pas entièrement d'accord avec les règles. Ce sont de bonnes lignes directrices à considérer avec les performances et la normalisation, mais pas à la lumière des possibilités.

Comme vous pouvez le voir dans les réponses, il existe de nombreuses façons créatives de les utiliser. Donc, ces directives doivent être simplement cela, toujours pour des raisons de performances et d'efficacité.

Dans ce cas, j'utilise des classes pour représenter des objets du monde réel dans leur plus grande forme, j'utilise des structures pour représenter des objets plus petits qui ont des utilisations plus exactes. Comme vous l'avez dit, "un tout plus cohérent". Le mot clé étant cohérent. Les classes seront davantage des éléments orientés objet, tandis que les structures peuvent avoir certaines de ces caractéristiques, bien qu'à une plus petite échelle. OMI.

Je les utilise beaucoup dans les balises Treeview et Listview où les attributs statiques communs sont accessibles très rapidement. J'ai toujours eu du mal à obtenir ces informations d'une autre manière. Par exemple, dans mes applications de base de données, j'utilise un Treeview où j'ai des tables, 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 et ainsi de suite. Je ne ferais pas ça avec un cours!

J'essaie de les garder petits, de les utiliser dans des situations à instance unique et de les empêcher de changer. Il est prudent de connaître la mémoire, l'allocation et les performances. Et les tests sont tellement nécessaires.

SnapJag
la source
Les structures peuvent être judicieusement utilisées pour représenter des objets immuables légers, ou elles peuvent être sensiblement utilisées pour représenter des ensembles fixes de variables liées mais indépendantes (par exemple les coordonnées d'un point). Les conseils sur cette page sont bons pour les structures qui sont conçues pour servir le premier objectif, mais sont incorrectes pour les structures qui sont conçues pour servir le dernier objectif. Ma pensée actuelle est que les structures qui ont des champs privés devraient généralement répondre à la description indiquée, mais de nombreuses structures devraient exposer leur état entier via des champs publics.
supercat
Si la spécification pour un type de "point 3D" indique que son état entier est exposé via les membres lisibles x, y et z, et qu'il est possible de créer une instance avec n'importe quelle combinaison de doublevaleurs pour ces coordonnées, une telle spécification l'obligerait à se comporter sémantiquement de façon identique à une structure de champ exposé à l'exception de certains détails du comportement multithread (la classe immuable serait meilleure dans certains cas, tandis que la structure de champ exposé serait meilleure dans d'autres; une structure dite "immuable" serait être pire dans tous les cas).
supercat
8

Ma règle est

1, utilisez toujours la classe;

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

rockXrock
la source
Un cas d'utilisation important que Microsoft ignore est celui où l'on veut qu'une variable de type Fooencapsule une collection fixe de valeurs indépendantes (par exemple les coordonnées d'un point) que l'on voudra parfois faire circuler en groupe et parfois changer indépendamment. Je n'ai trouvé aucun modèle d'utilisation de classes qui combine les deux objectifs presque aussi bien qu'une simple structure de champ exposé (qui, étant une collection fixe de variables indépendantes, convient parfaitement).
supercat
1
@supercat: Je pense qu'il n'est pas tout à fait juste de blâmer Microsoft pour cela. Le vrai problème ici est que C # en tant que langage orienté objet ne se concentre tout simplement pas sur les types d'enregistrement simples qui exposent uniquement les données sans trop de comportement. C # n'est pas un langage multi-paradigme dans la même mesure que par exemple C ++. Cela étant dit, je crois également que très peu de gens programment de la POO pure, donc peut-être que C # est un langage trop idéaliste. (Pour ma part, j'ai récemment commencé à exposer des public readonlychamps dans mes types, car la création de propriétés en lecture seule est tout simplement trop de travail pour pratiquement aucun avantage.)
stakx - ne contribuant plus
1
@stakx: Il n'est pas nécessaire qu'il se "concentre" sur de tels types; les reconnaître pour ce qu'ils sont suffirait. La plus grande faiblesse de C # en ce qui concerne les structures est également son plus gros problème dans de nombreux autres domaines: le langage fournit des installations inadéquates pour indiquer quand certaines transformations sont ou ne sont pas appropriées, et le manque de telles installations entraîne des décisions de conception malheureuses. Par exemple, 99% des "structures mutables sont mauvaises" proviennent de la transformation du compilateur MyListOfPoint[3].Offset(2,3);en var temp=MyListOfPoint[3]; temp.Offset(2,3);une transformation qui est fausse lorsqu'elle est appliquée ...
supercat
... à la Offsetméthode. La bonne façon d'empêcher un tel code faux ne devrait pas être de rendre les structures inutilement immuables, mais plutôt de permettre à des méthodes comme Offsetd'être étiquetées avec un attribut interdisant la transformation susmentionnée. Les conversions numériques implicites auraient également pu être bien meilleures si elles pouvaient être étiquetées de manière à ne s'appliquer que dans les cas où leur invocation serait évidente. Si des surcharges existent pour foo(float,float)et foo(double,double), je dirais qu'essayer d'utiliser a floatet a doublene devrait pas souvent appliquer une conversion implicite, mais devrait plutôt être une erreur.
supercat
Une affectation directe d'une doublevaleur à a float, ou la passer à une méthode qui peut prendre un floatargument mais pas double, ferait presque toujours ce que le programmeur voulait. En revanche, attribuer une floatexpression à doublesans transtypage explicite est souvent une erreur. Le seul moment où la double->floatconversion implicite entraînerait des problèmes serait le moment où elle entraînerait la sélection d'une surcharge non idéale. Je suppose que la bonne façon d'empêcher cela n'aurait pas dû être d'interdire impliciter double-> float, mais de baliser les surcharges avec des attributs pour interdire la conversion.
supercat
8

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 d'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 affecté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 sont utilisées pour modéliser un comportement plus complexe ou des données destinées à être modifiées après la création d'un objet de classe.

Classes et structures (Guide de programmation C #)

J_hajian_nzd
la source
Les structures sont également très bonnes dans les cas où il est nécessaire de fixer quelques variables liées mais indépendantes avec du ruban adhésif (par exemple les coordonnées d'un point). Les directives MSDN sont raisonnables si l'on essaie de produire des structures qui se comportent comme des objets, mais sont beaucoup moins appropriées lors de la conception d'agrégats; certains d'entre eux ont presque exactement tort dans cette dernière situation. Par exemple, plus le degré d'indépendance des variables encapsulées par un type est grand, plus l'avantage d'utiliser une structure à champ exposé plutôt qu'une classe immuable est grand.
supercat
6

MYTHE N ° 1: LES STRUCTS SONT DES CLASSES LÉGÈRES

Ce mythe se présente sous diverses formes. Certaines personnes croient que les types de valeur ne peuvent pas 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 qu'il soit un type de valeur, en termes d'être une unité fondamentale comme un nombre ou un caractère, et il est également logique qu'il puisse effectuer des calculs basés sur Sa valeur. En regardant les choses dans l'autre sens, les types de transfert de données doivent souvent être des types de référence de toute façon - la décision doit être basée sur la valeur souhaitée ou la sémantique du type de référence, pas 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, n'ont pas la surcharge d'identification de type et ne nécessitent pas de déréférencement, par exemple. Mais à d'autres égards, les types de référence sont plus performants: le passage de paramètres, l'attribution de valeurs à des variables, le retour de valeurs et des opérations similaires ne nécessitent que 4 ou 8 octets pour être copiés (selon que vous exécutez 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 «pur», et passer une expression ArrayList à une méthode impliquait de copier toutes ses données! Dans presque tous les cas, les performances ne sont pas vraiment déterminées par ce type de décision de toute façon. Les goulots d'étranglement ne sont presque jamais là où vous pensez qu'ils seront, 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 (que ce soit une classe ou 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 occupée pour le code lui-même, mais il est engagé une fois plutôt que pour chaque instance.)

MYTHE # 2: LES TYPES DE RÉFÉRENCE VIVENT SUR LE CŒUR; TYPES DE VALEURS EN DIRECT 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 vit partout où elle est déclarée, donc 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à 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 vivent sur la pile. En C # 2 et versions ultérieures, même certaines variables locales ne vivent pas vraiment sur la pile, comme vous le verrez lorsque nous examinons les méthodes anonymes au chapitre 5. CES CONCEPTS SONT-ILS PERTINENTS MAINTENANT? On peut soutenir que si vous écrivez du code managé, vous devez laisser le runtime s'inquiéter de la meilleure façon d'utiliser la mémoire. En effet, la spécification de la langue ne garantit pas ce qui vit où; un futur runtime 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 du code qui utilise à peine la pile. Le mythe suivant n'est généralement qu'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) comment se comporte réellement C #, mais elles ne savent pas ce que signifie «passer par référence». Malheureusement, cela prête à confusion pour les personnes qui savent ce que cela signifie. La définition formelle du passage par référence est relativement compliquée, impliquant des valeurs l et une terminologie informatique similaire, mais l'important est que si vous passez une variable par référence, la méthode que vous appelez peut changer la valeur de la variable de l'appelant en modifiant sa valeur de paramètre. Maintenant, 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 transmis par référence. Par exemple,

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 passée par valeur. Si vous deviez changer la valeur de la variable de générateur dans la méthode - par exemple, avec l'instruction builder = null; - ce changement ne serait pas vu 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 aussi le bit «les 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 transmise par référence ou la valeur de l'argument (la référence) est transmise par valeur. En plus de 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 transmis, cela causerait des problèmes, car il n'y aurait pas d'objet à passer! Au lieu, la référence nulle 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 va beaucoup plus en détail. Ces mythes ne sont pas les seuls à exister. La boxe et le déballage arrivent pour leur juste part de malentendus, que j'essaierai de clarifier ensuite.

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

habib
la source
1
Très bien en supposant que vous ayez raison. Très bon également d'ajouter une référence.
NoChance
5

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

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

Si vous avez désespérément besoin de perf, considérez-les, mais mesurez toujours.

Brian
la source
24
Je ne suis pas d'accord avec cette réponse. Les structures ont une utilisation légitime dans de nombreux scénarios. Voici un exemple - marshaling des processus croisés de données de manière atomique.
Franci Penov
25
Vous devez éditer votre message et développer vos points - vous avez donné votre avis, mais vous devez le sauvegarder en expliquant pourquoi vous prenez cet avis.
Erik Forbes
4
Je pense qu'ils ont besoin d'un équivalent de la carte Totin 'Chip ( en.wikipedia.org/wiki/Totin%27_Chip ) pour utiliser les structures. Sérieusement.
Greg
4
Comment une personne de 87,5 K publie-t-elle une réponse comme celle-ci? L'a-t-il fait alors qu'il était enfant?
Rohit Vipin Mathews
3
@Rohit - c'était il y a six ans; les normes du site étaient alors très différentes. c'est toujours une mauvaise réponse, cependant, vous avez raison.
Andrew Arnold
5

Je venais de traiter avec le canal nommé Windows Communication Foundation [WCF] et j'ai remarqué qu'il est logique d'utiliser Structs afin de garantir que l'échange de données est de type valeur au lieu de type référence .

N_E
la source
1
Ceci est le meilleur indice de tous, à mon humble avis.
Ivan
4

La structure C # est une alternative légère à une classe. Il peut faire presque la même chose qu'une classe, mais il est moins «coûteux» 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ù les structures nouvellement instanciées sont placées sur la pile. De plus, vous ne traitez pas avec des références à des structures, comme avec des classes, mais à la place 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 comme référence. Il y a plus à ce sujet dans le chapitre sur les paramètres de fonction.

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

Saeed Dini
la source
3

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 où cela peut être un tueur. Comme les grands caches dans les applications à faible latence. Voir cet article pour un exemple:

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

lapin
la source
3

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

  1. Si vous souhaitez empêcher la collecte de l'objet 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é vers d'autres types.

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

Vikram
la source
3

En bref, utilisez struct si:

1- Les propriétés / champs de votre objet n'ont pas besoin d'être modifiés. Je veux dire que vous voulez simplement leur donner une valeur initiale, puis les lire.

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

Si c'est le cas, vous pouvez profiter des structures pour de meilleures performances et une allocation de mémoire optimisée car elles n'utilisent que des piles plutôt que des piles et des tas (en classes)

akazemis
la source
2

J'utilise rarement une structure pour les choses. Mais c'est juste moi. Cela dépend si j'ai besoin que l'objet soit nul ou non.

Comme indiqué dans d'autres réponses, j'utilise des classes pour des objets du monde réel. J'ai également l'état d'esprit des structures utilisées pour stocker de petites quantités de données.

Flexo
la source
-11

Les structures sont dans la plupart des cas comme des classes / objets. La structure peut contenir des fonctions, des membres et peut être héritée. Mais les structures sont en C # utilisées uniquement pour la conservation des données . Les structures prennent moins de RAM que les classes et sont plus faciles à collecter pour le garbage collector . Mais lorsque vous utilisez des fonctions dans votre structure, le compilateur prend en fait cette structure de la même manière que classe / objet, donc si vous voulez quelque chose avec des fonctions, alors utilisez classe / objet .

Cryo Erger
la source
2
Les structures ne peuvent PAS être héritées, voir msdn.microsoft.com/en-us/library/0taef578.aspx
HimBromBeere