Question Quand utiliser struct?


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

Je suis tombé sur ces règles ici:

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

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


1201
2018-02-06 17:37


origine


Réponses:


La source référencée par le PO a une certaine crédibilité ... mais qu'en est-il de Microsoft - quelle est la position sur l'utilisation de la structure? J'ai cherché un peu plus apprendre de Microsoftet 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és dans   d'autres objets.

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

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

Microsoft viole systématiquement ces règles

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

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

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

*Source de référence

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

Regardons pourquoi Microsoft utiliserait ces structures:

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

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

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

  1. Rien dans les structures ci-dessus n'est déclaré readonly - ne 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 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é: 5ms
  Temps total à remplir: 889ms

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 (assez précise pour nos objectifs).

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

Temps total à remplir: certes faussé en raison de l'exploitation forestière et un OnResize événement que j'ai ajouté à la source; Cependant, toujours impressionnant de remplir 300k entiers tout en redimensionnant 15 fois pendant l'opération. Juste par curiosité, quel serait le temps total à remplir si je connaissais déjà la capacité? 13ms 

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

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

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

Qu'est-ce qui se passe est, parce que Entry est 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 de valeur. Pour utiliser Entry en tant que 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 comme un type de référence peut être trouvé à MSDN: Conception de structure. En bref:

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

Si une structure définit un constructeur par défaut, lorsque les tableaux de   structure sont créés, le langage commun s'exécute automatiquement   exécute le constructeur par défaut sur chaque élément du 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 Asimov Trois lois de la robotique:

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

...qu'est-ce que nous prenons de cette: en bref, être 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 non intentionnelles).


541
2017-08-07 13:44



Chaque fois que vous n'avez pas besoin de polymorphisme, recherchez la valeur sémantique et souhaitez éviter l'allocation de tas et le surcoût de la récupération de place associée. La mise en garde, cependant, est que les structures (arbitrairement grandes) sont plus chères à transmettre que les références de classe (généralement un mot machine), de sorte que les classes pourraient finir par être plus rapides en pratique.


142
2018-02-06 17:40



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

1) Vous utilisez des structures pour les performances lorsque vous les stockez dans des tableaux. (voir également Quand sont les structs 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) en cours de cession et en passant comme arguments, ce qui peut conduire à un comportement inattendu; ceci est particulièrement dangereux si la personne qui regarde le code pas savoir qu'ils ont affaire à une structure.
  • Ils ne peuvent pas être hérités.
  • Passer des structures comme arguments est plus cher que les classes.

133
2018-02-28 16:33



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 pourquoi les gens sont downvoting ceci mais c'est un point valide, et a été fait avant que l'op clarifie sa question, et c'est la raison fondamentale la plus fondamentale pour 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.


82
2018-02-06 17:40



En plus de la réponse "c'est une valeur", un scénario spécifique pour l'utilisation des structures est quand vous connaître que vous avez un ensemble de données qui provoque des problèmes de récupération de place, et vous avez beaucoup d'objets. Par exemple, une liste / tableau important d'instances de personnes. La métaphore naturelle est ici une classe, mais si vous avez un grand nombre d'instances de personnes ayant une longue durée de vie, elles peuvent finir par obstruer GEN-2 et causer des décrochages au GC. Si le scénario le justifie, une approche potentielle consiste à utiliser un tableau (et non une liste) de personnes. structs, c'est à dire. Person[]. Maintenant, au lieu d'avoir des millions d'objets dans GEN-2, vous avez un seul morceau sur le LOH (je suppose qu'il n'y a pas de chaînes 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 surdimensionnées pour une structure, et vous ne voulez pas copier les valeurs de graisse tout le temps. Cependant, l'accès direct dans un tableau ne copie pas la structure - il est en place (contrairement à un indexeur de liste, qui copie). Cela signifie beaucoup de travail avec les index:

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

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

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

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

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


54
2017-10-22 12:14



Du C # Spécification du langage:

1.7 Structures 

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   types de valeur et ne nécessitent pas d'allocation de tas. Une variable d'une structure   type stocke directement les données de la structure, alors qu'une variable d'un   Le type de classe stocke une référence à un objet alloué dynamiquement.   Les types de structure ne prennent pas en charge l'héritage spécifié par l'utilisateur, et toutes les structures   types héritent implicitement de l'objet type.

Les structures sont particulièrement utiles pour les petites structures de données qui ont   sémantique des valeurs. Numéros complexes, points dans un système de coordonnées ou   Les paires clé-valeur d'un dictionnaire sont de bons exemples de structures. le   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 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   Les objets séparés sont instanciés: un pour le tableau et un pour chacun   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 du point une structure.

struct Point
{
   public int x, y;

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

Maintenant, 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 appelé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 que les opérations sur une variable affectent l'objet référencé par l'autre variable. Avec les structs, 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 à a.x.

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ètre 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 aux structures, ce qui exclut leur utilisation dans un certain nombre de situations.


36
2017-09-17 15:42



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


31
2018-02-06 17:58



Voici une règle de base.

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

  • Si un champ membre est un type de référence, créez un classe. C'est parce que le champ de type de référence aura besoin de l'allocation de tas de toute façon.

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
}

24
2018-01-22 10:17