Quelle est la différence entre struct et class dans .NET?

Réponses:

1058

Dans .NET, il existe deux catégories de types, les types de référence et les types de valeur .

Les structures sont des types de valeur et les classes sont des types de référence .

La différence générale est qu'un type de référence vit sur le tas et qu'un type de valeur vit en ligne, c'est-à-dire, où que ce soit votre variable ou champ est défini.

Une variable contenant un type de valeur contient la valeur entière du type de valeur. Pour une structure, cela signifie que la variable contient la structure entière, avec tous ses champs.

Une variable contenant un type de référence contient un pointeur ou une référence à un autre endroit de la mémoire où réside la valeur réelle.

Cela présente un avantage, pour commencer:

  • les types de valeur contiennent toujours une valeur
  • les types de référence peuvent contenir une référence nulle , ce qui signifie qu'ils ne font référence à rien du tout pour le moment

En interne, les types de référence sont implémentés en tant que pointeurs, et sachant que, et sachant comment fonctionne l'affectation des variables, il existe d'autres modèles de comportement:

  • copier le contenu d'une variable de type valeur dans une autre variable, copie tout le contenu dans la nouvelle variable, ce qui rend les deux distinctes. En d'autres termes, après la copie, les modifications apportées à l'une n'affecteront pas l'autre
  • copier le contenu d'une variable de type référence dans une autre variable, copie la référence, ce qui signifie que vous avez maintenant deux références vers le même stockage ailleurs des données réelles. En d'autres termes, après la copie, la modification des données dans une référence semblera également affecter l'autre, mais uniquement parce que vous regardez vraiment les mêmes données aux deux endroits

Lorsque vous déclarez des variables ou des champs, voici comment les deux types diffèrent:

  • variable: le type de valeur se trouve sur la pile, le type de référence se trouve sur la pile en tant que pointeur vers quelque part dans la mémoire du tas où se trouve la mémoire réelle (bien que la série d'articles d'Eric Lipperts: la pile est un détail d'implémentation) ).
  • class / struct-field: le type de valeur vit complètement à l'intérieur du type, le type de référence vit à l'intérieur du type en tant que pointeur vers quelque part dans la mémoire du tas où se trouve la mémoire réelle.
Lasse V. Karlsen
la source
43
Dans un souci de complétude, je dois mentionner qu'Eric Lippert a dit que la pile est un détail d'implémentation , chaque fois que je mentionne la pile ci-dessus, pensez aux messages d'Eric.
Lasse V. Karlsen
2
Est-ce que tout cela est également valable pour C ++?
Koray Tugay
9
une autre différence cruciale est l'utilisation. À partir de MSDN: "les structures sont généralement utilisées pour encapsuler un petit groupe de variables liées, telles que les coordonnées du rectangle. Les structures peuvent également contenir des constructeurs, des constantes, des champs, des méthodes, des propriétés, des indexeurs, des opérateurs, des événements et des types imbriqués, même si plusieurs sont obligatoires, vous devriez plutôt envisager de faire de votre type une classe. "
thewpfguy
4
@KorayTugay Non, ce n'est pas le cas.
ZoomIn
9
@KorayTugay dans la structure et la classe C ++ sont absolument équivalents, sauf pour une chose - restriction d'accès par défaut (la classe a privé par défaut, struct a public)
berkus
207

Un bref résumé de chacun:

Cours uniquement:

  • Peut supporter l'héritage
  • Sont des types de référence (pointeur)
  • La référence peut être nulle
  • Avoir une surcharge de mémoire par nouvelle instance

Structures uniquement:

  • Ne peut pas prendre en charge l'héritage
  • Sont des types de valeur
  • Sont passés par valeur (comme des entiers)
  • Ne peut pas avoir une référence nulle (sauf si Nullable est utilisé)
  • Ne pas avoir de surcharge de mémoire par nouvelle instance - sauf si «encadré»

Classes et structures:

  • Les types de données composés sont-ils généralement utilisés pour contenir quelques variables qui ont une relation logique
  • Peut contenir des méthodes et des événements
  • Peut prendre en charge les interfaces
Thomas Bratt
la source
16
Certaines parties de cette réponse ne sont pas tout à fait exactes. Les classes ne vont pas toujours sur le tas et les structures ne vont pas toujours sur la pile. Les exceptions actuelles incluent les champs de structure d'une classe, les variables capturées dans les méthodes anonymes et les expressions lambda, les blocs d'itérateur et les valeurs encadrées déjà mentionnées. Mais l'allocation pile vs tas est un détail d'implémentation et peut être sujette à changement. Eric lippart en parle ici . J'ai voté contre, mais je le supprimerai volontiers si vous mettez à jour.
Simon P Stevens
1
Les structures ne prennent pas en charge l'héritage d'autres structures / classes, mais vous POUVEZ implémenter une interface sur une structure.
thewpfguy
2
Vous voudrez peut-être clarifier ce que vous voulez dire lorsque vous prétendez que la structure "n'a pas de surcharge mémoire par nouvelle instance" . Ma première interprétation était que vous affirmiez - évidemment de façon absurde - que les structures utilisent zéro mémoire. Ensuite, j'ai pensé que vous essayez peut-être de dire qu'une structure, contrairement à une classe, nécessite exactement autant de mémoire que la somme de ses champs membres, et pas plus. Mais j'ai cherché sur Google c# struct memory overheadet j'ai trouvé cette réponse de Hans Passant qui dit que non, ce n'est pas le cas non plus. Alors qu'est - ce que tu veux dire?
Mark Amery
4
@MarkAmery J'ai eu la même réaction initiale que vous avez identifiée à l'expression "pas de surcharge de mémoire", mais je pense que l'OP fait référence au fait que les instances de classsont de la mémoire gérée (gérée par le garbage collector), tandis que les instances de structne le sont pas .
Hutch
1
"Struct sont passés par valeur (comme des entiers)" est faux: toutes les variables sont passées par valeur, également le type de référence. Si vous souhaitez transmettre une variable par référence, vous devez utiliser le mot clé "ref". jonskeet.uk/csharp/parameters.html#ref
Marco Staffoli
41

Dans .NET, les déclarations de structure et de classe différencient les types de référence et les types de valeur.

Lorsque vous passez un type de référence, un seul est réellement stocké. Tout le code qui accède à l'instance accède au même.

Lorsque vous passez un type de valeur, chacun est une copie. Tout le code fonctionne sur sa propre copie.

Cela peut être montré avec un exemple:

struct MyStruct 
{
    string MyProperty { get; set; }
}

void ChangeMyStruct(MyStruct input) 
{ 
   input.MyProperty = "new value";
}

...

// Create value type
MyStruct testStruct = new MyStruct { MyProperty = "initial value" }; 

ChangeMyStruct(testStruct);

// Value of testStruct.MyProperty is still "initial value"
// - the method changed a new copy of the structure.

Pour une classe, ce serait différent

class MyClass 
{
    string MyProperty { get; set; }
}

void ChangeMyClass(MyClass input) 
{ 
   input.MyProperty = "new value";
}

...

// Create reference type
MyClass testClass = new MyClass { MyProperty = "initial value" };

ChangeMyClass(testClass);

// Value of testClass.MyProperty is now "new value" 
// - the method changed the instance passed.

Les classes ne peuvent être rien - la référence peut pointer vers une valeur nulle.

Les structures sont la valeur réelle - elles peuvent être vides mais jamais nulles. Pour cette raison, les structures ont toujours un constructeur par défaut sans paramètres - elles ont besoin d'une «valeur de départ».

Keith
la source
@ T.Todua oui, il y a de meilleures réponses ci-dessus, que j'ai voté et choisi comme réponse après avoir fourni celle-ci - cela vient de la première version bêta de SO alors que nous étions encore en train de déterminer les règles.
Keith
1
Je ne sais pas si vous m'avez bien compris, j'ai vraiment voté / accepté votre réponse (par opposition aux réponses ci-dessus), car la vôtre avait de bons exemples (pas seulement une explication théorique, par opposition à la réponse ci-dessus, qui n'avait que des explications théoriques sans exemples) ).
T.Todua
24

Différence entre Structs et Classes:

  • Les structures sont de type valeur tandis que les classes sont de type référence .
  • Les structures sont stockées sur la pile tandis que les classes sont stockées sur le tas .
  • Les types de valeur conservent leur valeur dans la mémoire où ils sont déclarés, mais le type de référence contient une référence à une mémoire d'objet.
  • Types de valeur détruits immédiatement après la perte de la portée, tandis que le type de référence ne détruit que la variable après la perte de la portée. L'objet est ensuite détruit par le garbage collector.
  • Lorsque vous copiez une structure dans une autre structure, une nouvelle copie de cette structure est créée modifiée d'une structure n'affectera pas la valeur de l'autre structure.
  • Lorsque vous copiez une classe dans une autre classe, elle copie uniquement la variable de référence.
  • Les deux variables de référence pointent vers le même objet sur le tas. Le passage à une variable affectera l'autre variable de référence.
  • Les structures ne peuvent pas avoir de destructeurs , mais les classes peuvent avoir des destructeurs.
  • Les structures ne peuvent pas avoir de constructeurs explicites sans paramètre, alors qu'une classe can struct ne prend pas en charge l'héritage, mais les classes le font. Les deux prennent en charge l'héritage d'une interface.
  • Les structures sont de type scellé .
shana
la source
21

Du choix de Microsoft entre la classe et la structure ...

En règle générale, la majorité des types dans un framework doivent être des classes. Il existe cependant certaines situations dans lesquelles les caractéristiques d'un type de valeur rendent plus approprié l'utilisation de structures.

CONSIDÉRER 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.

X ÉVITEZ une structure à moins que le type n'ait toutes les caractéristiques suivantes:

  • Il représente logiquement une valeur unique, similaire aux types primitifs (int, double, etc.).
  • Il a une taille d'instance inférieure à 16 octets.
  • C'est immuable. (ne peut pas être modifié)
  • Il n'aura pas à être emballé fréquemment.
Sunsetquest
la source
19

En plus de toutes les différences décrites dans les autres réponses:

  1. Les structures ne peuvent pas avoir de constructeur explicite sans paramètre alors qu'une classe peut
  2. Les structures ne peuvent pas avoir de destructeurs , alors qu'une classe peut
  3. Les structures ne peuvent pas hériter d'une autre structure ou classe alors qu'une classe peut hériter d'une autre classe. (Les structures et les classes peuvent être implémentées à partir d'une interface.)

Si vous recherchez une vidéo expliquant toutes les différences, vous pouvez consulter la Partie 29 - Tutoriel C # - Différence entre les classes et les structures en C # .

Venkat
la source
4
Beaucoup plus significatif que le fait que les langages .net ne permettent généralement pas à une structure de définir un constructeur sans paramètre (la décision de l'autoriser ou non est prise par le compilateur de langage) est le fait que les structures peuvent exister et être exposées vers le monde extérieur sans qu'aucune sorte de constructeur n'ait été exécutée (même lorsqu'un constructeur sans paramètre est défini). La raison pour laquelle les langages .net interdisent généralement les constructeurs sans paramètres pour les structures est d'éviter la confusion qui résulterait de l'exécution de tels constructeurs, parfois non.
supercat
15

Les instances de classes sont stockées sur le tas géré. Toutes les variables «contenant» une instance sont simplement une référence à l'instance sur le tas. Passer un objet à une méthode entraîne la transmission d'une copie de la référence, et non l'objet lui-même.

Les structures (techniquement, les types de valeur) sont stockées partout où elles sont utilisées, un peu comme un type primitif. Le contenu peut être copié par le runtime à tout moment et sans invoquer un constructeur de copie personnalisé. La transmission d'un type de valeur à une méthode implique la copie de la valeur entière, encore une fois sans invoquer de code personnalisable.

La distinction est améliorée par les noms C ++ / CLI: "ref class" est une classe telle que décrite en premier, "value class" est une classe telle que décrite en second. Les mots-clés "classe" et "struct" tels qu'utilisés par C # sont simplement quelque chose qui doit être appris.

Zooba
la source
11
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
|                        |                                                Struct                                                |                                               Class                                               |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
| Type                   | Value-type                                                                                           | Reference-type                                                                                    |
| Where                  | On stack / Inline in containing type                                                                 | On Heap                                                                                           |
| Deallocation           | Stack unwinds / containing type gets deallocated                                                     | Garbage Collected                                                                                 |
| Arrays                 | Inline, elements are the actual instances of the value type                                          | Out of line, elements are just references to instances of the reference type residing on the heap |
| Aldel Cost             | Cheap allocation-deallocation                                                                        | Expensive allocation-deallocation                                                                 |
| Memory usage           | Boxed when cast to a reference type or one of the interfaces they implement,                         | No boxing-unboxing                                                                                |
|                        | Unboxed when cast back to value type                                                                 |                                                                                                   |
|                        | (Negative impact because boxes are objects that are allocated on the heap and are garbage-collected) |                                                                                                   |
| Assignments            | Copy entire data                                                                                     | Copy the reference                                                                                |
| Change to an instance  | Does not affect any of its copies                                                                    | Affect all references pointing to the instance                                                    |
| Mutability             | Should be immutable                                                                                  | Mutable                                                                                           |
| Population             | In some situations                                                                                   | Majority of types in a framework should be classes                                                |
| Lifetime               | Short-lived                                                                                          | Long-lived                                                                                        |
| Destructor             | Cannot have                                                                                          | Can have                                                                                          |
| Inheritance            | Only from an interface                                                                               | Full support                                                                                      |
| Polymorphism           | No                                                                                                   | Yes                                                                                               |
| Sealed                 | Yes                                                                                                  | When have sealed keyword                                                                          |
| Constructor            | Can not have explicit parameterless constructors                                                     | Any constructor                                                                                   |
| Null-assignments       | When marked with nullable question mark                                                              | Yes (+ When marked with nullable question mark in C# 8+)                                          |
| Abstract               | No                                                                                                   | When have abstract keyword                                                                        |
| Member Access Modifiers| public, private, internal                                                                            | public, protected, internal, protected internal, private protected                                |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
0xaryan
la source
1
C'est en fait assez splendide: résumé et informatif. N'oubliez pas de relire votre réponse au moins une fois - vous avez échangé des explications sur la structure et la classe dans certaines lignes, il y a aussi des fautes de frappe.
Robert Synoradzki
1
@ensisNoctis Désolé pour ces erreurs et merci pour la modification. Je devrais relire mes réponses 😅
0xaryan
8

Structure vs classe

Une structure est un type de valeur, elle est donc stockée sur la pile, mais une classe est un type de référence et est stockée sur le tas.

Une structure ne prend pas en charge l'héritage et le polymorphisme, mais une classe prend en charge les deux.

Par défaut, tous les membres de la structure sont publics, mais les membres de la classe sont par défaut de nature privée.

Comme une structure est un type de valeur, nous ne pouvons pas affecter null à un objet struct, mais ce n'est pas le cas pour une classe.

Swagatika dhal
la source
5
Concernant "tous les membres struct sont publics": Si je ne me trompe pas, c'est incorrect. "Le niveau d'accès pour les membres de classe et les membres de structure, y compris les classes et structures imbriquées, est privé par défaut." msdn.microsoft.com/en-us/library/ms173121.aspx
Nate Cook
8

Pour ajouter aux autres réponses, il y a une différence fondamentale qui mérite d'être notée, et c'est la façon dont les données sont stockées dans les tableaux car cela peut avoir un effet majeur sur les performances.

  • Avec une structure, le tableau contient l'instance de la structure
  • Avec une classe, le tableau contient un pointeur vers une instance de la classe ailleurs en mémoire

Donc, un tableau de structures ressemble à ceci en mémoire

[struct][struct][struct][struct][struct][struct][struct][struct]

Alors qu'un tableau de classes ressemble à ceci

[pointer][pointer][pointer][pointer][pointer][pointer][pointer][pointer]

Avec un tableau de classes, les valeurs qui vous intéressent ne sont pas stockées dans le tableau, mais ailleurs dans la mémoire.

Pour une grande majorité d'applications, cette différence n'a pas vraiment d'importance, cependant, dans le code haute performance, cela affectera la localisation des données dans la mémoire et aura un impact important sur les performances du cache CPU. Utiliser des classes quand vous auriez pu / auriez dû utiliser des structures augmentera massivement le nombre de cache manquant sur le CPU.

La chose la plus lente qu'un processeur moderne ne fait pas de croquer les nombres, c'est de récupérer des données de la mémoire, et un hit de cache L1 est beaucoup plus rapide que de lire des données de la RAM.

Voici du code que vous pouvez tester. Sur ma machine, l'itération à travers le tableau de classe prend environ 3 fois plus longtemps que le tableau struct.

    private struct PerformanceStruct
    {
        public int i1;
        public int i2;
    }

    private class PerformanceClass
    {
        public int i1;
        public int i2;
    }

    private static void DoTest()
    {
        var structArray = new PerformanceStruct[100000000];
        var classArray = new PerformanceClass[structArray.Length];

        for (var i = 0; i < structArray.Length; i++)
        {
            structArray[i] = new PerformanceStruct();
            classArray[i] = new PerformanceClass();
        }

        long total = 0;
        var sw = new Stopwatch();
        sw.Start();
        for (var loops = 0; loops < 100; loops++)
        for (var i = 0; i < structArray.Length; i++)
        {
            total += structArray[i].i1 + structArray[i].i2;
        }

        sw.Stop();
        Console.WriteLine($"Struct Time: {sw.ElapsedMilliseconds}");
        sw = new Stopwatch();
        sw.Start();
        for (var loops = 0; loops < 100; loops++)
        for (var i = 0; i < classArray.Length; i++)
        {
            total += classArray[i].i1 + classArray[i].i2;
        }

        Console.WriteLine($"Class Time: {sw.ElapsedMilliseconds}");
    }
Will Calderwood
la source
-1; "Les structures sont des types de valeur, donc elles stockent une valeur, les classes sont des types de référence, donc elles référencent une classe." est peu clair et peu susceptible d'avoir un sens pour quiconque ne l'a pas déjà compris dans les autres réponses ici, et "Avec une classe, la classe contenant contiendra juste un pointeur vers la nouvelle classe dans une zone de mémoire différente." confond les classes avec les instances de classe.
Mark Amery
@MarkAmery J'ai essayé de clarifier légèrement. Le point que j'essayais vraiment de faire était la différence dans la façon dont les tableaux fonctionnent avec les types de valeur et de référence et l'effet que cela a sur les performances. Je n'essayais pas de ré-expliquer ce que sont les types de valeur et de référence, car cela se fait dans de nombreuses autres réponses.
Will Calderwood
7

Juste pour le compléter, il y a une autre différence lors de l'utilisation du Equals méthode, qui est héritée par toutes les classes et structures.

Disons que nous avons une classe et une structure:

class A{
  public int a, b;
}
struct B{
  public int a, b;
}

et dans la méthode Main, nous avons 4 objets.

static void Main{
  A c1 = new A(), c2 = new A();
  c1.a = c1.b = c2.a = c2.b = 1;
  B s1 = new B(), s2 = new B();
  s1.a = s1.b = s2.a = s2.b = 1;
}

Alors:

s1.Equals(s2) // true
s1.Equals(c1) // false
c1.Equals(c2) // false
c1 == c2 // false

Ainsi , les structures sont adaptées aux objets de type numérique, comme les points (enregistrer les coordonnées x et y). Et les cours conviennent aux autres. Même si 2 personnes ont le même nom, la même taille, le même poids ..., ce sont quand même 2 personnes.

Ning
la source
6

Eh bien, pour commencer, une structure est passée par valeur plutôt que par référence. Les structures sont bonnes pour les structures de données relativement simples, tandis que les classes ont beaucoup plus de flexibilité d'un point de vue architectural via le polymorphisme et l'héritage.

D'autres peuvent probablement vous donner plus de détails que moi, mais j'utilise des structures lorsque la structure que je recherche est simple.

Ed S.
la source
4

Outre la différence de base du spécificateur d'accès, et quelques-uns mentionnés ci-dessus, je voudrais ajouter quelques-unes des principales différences, y compris quelques-unes des mentionnées ci-dessus avec un exemple de code avec sortie, qui donnera une idée plus claire de la référence et de la valeur

Structures:

  • Sont des types de valeur et ne nécessitent pas d'allocation de segment.
  • L'allocation de mémoire est différente et est stockée dans la pile
  • Utile pour les petites structures de données
  • Affecter les performances, lorsque nous transmettons de la valeur à la méthode, nous transmettons la structure de données entière et tout est transmis à la pile.
  • Le constructeur 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
  • 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.
  • Ne prennent pas en charge l'héritage spécifié par l'utilisateur et héritent implicitement de l'objet type

Classe:

  • Valeur du type de référence
  • Stocké dans le tas
  • Stocker une référence à un objet alloué dynamiquement
  • Les constructeurs sont invoqués avec le nouvel opérateur, mais cela n'alloue pas de mémoire sur le tas
  • Plusieurs variables peuvent avoir une référence au même objet
  • Il est possible que les opérations sur une variable affectent l'objet référencé par l'autre variable

Exemple de code

    static void Main(string[] args)
    {
        //Struct
        myStruct objStruct = new myStruct();
        objStruct.x = 10;
        Console.WriteLine("Initial value of Struct Object is: " + objStruct.x);
        Console.WriteLine();
        methodStruct(objStruct);
        Console.WriteLine();
        Console.WriteLine("After Method call value of Struct Object is: " + objStruct.x);
        Console.WriteLine();

        //Class
        myClass objClass = new myClass(10);
        Console.WriteLine("Initial value of Class Object is: " + objClass.x);
        Console.WriteLine();
        methodClass(objClass);
        Console.WriteLine();
        Console.WriteLine("After Method call value of Class Object is: " + objClass.x);
        Console.Read();
    }
    static void methodStruct(myStruct newStruct)
    {
        newStruct.x = 20;
        Console.WriteLine("Inside Struct Method");
        Console.WriteLine("Inside Method value of Struct Object is: " + newStruct.x);
    }
    static void methodClass(myClass newClass)
    {
        newClass.x = 20;
        Console.WriteLine("Inside Class Method");
        Console.WriteLine("Inside Method value of Class Object is: " + newClass.x);
    }
    public struct myStruct
    {
        public int x;
        public myStruct(int xCons)
        {
            this.x = xCons;
        }
    }
    public class myClass
    {
        public int x;
        public myClass(int xCons)
        {
            this.x = xCons;
        }
    }

Production

La valeur initiale de Struct Object est: 10

Méthode de structure interne La valeur de méthode interne de l'objet Struct est: 20

Après la valeur d'appel de méthode de l'objet Struct est: 10

La valeur initiale de l'objet de classe est: 10

Méthode de classe interne La valeur de méthode interne de l'objet classe est: 20

Après que la valeur d'appel de méthode de l'objet de classe soit: 20

Ici, vous pouvez clairement voir la différence entre l'appel par valeur et l'appel par référence.

Arijit Mukherjee
la source
4
  1. Les événements déclarés dans une classe ont leur accès + = et - = automatiquement verrouillé via un verrou (this) pour les rendre sûrs pour les threads (les événements statiques sont verrouillés sur le type de la classe). Les événements déclarés dans une structure n'ont pas leur accès + = et - = automatiquement verrouillé. Un verrou (ceci) pour une structure ne fonctionnerait pas car vous ne pouvez verrouiller qu'une expression de type référence.

  2. La création d'une instance de structure ne peut pas provoquer un garbage collection (sauf si le constructeur crée directement ou indirectement une instance de type référence) tandis que la création d'une instance de type reference peut provoquer un garbage collection.

  3. Une structure a toujours un constructeur public par défaut intégré.

    class DefaultConstructor
    {
        static void Eg()
        {
            Direct     yes = new   Direct(); // Always compiles OK
            InDirect maybe = new InDirect(); // Compiles if constructor exists and is accessible
            //...
        }
    }

    Cela signifie qu'une structure est toujours instanciable alors qu'une classe peut ne pas l'être car tous ses constructeurs peuvent être privés.

    class NonInstantiable
    {
        private NonInstantiable() // OK
        {
        }
    }
    
    struct Direct
    {
        private Direct() // Compile-time error
        {
        }
    }
  4. Une structure ne peut pas avoir de destructeur. Un destructeur est juste un remplacement d'objet.Finalize déguisé, et les structures, étant des types de valeur, ne sont pas soumises au garbage collection.

    struct Direct
    {
        ~Direct() {} // Compile-time error
    }
    class InDirect
    {
        ~InDirect() {} // Compiles OK
    }
    
    And the CIL for ~Indirect() looks like this:
    
    .method family hidebysig virtual instance void
            Finalize() cil managed
    {
      // ...
    } // end of method Indirect::Finalize
  5. Une structure est implicitement scellée, une classe ne l'est pas.
    Une structure ne peut pas être abstraite, une classe peut.
    Une structure ne peut pas appeler: base () dans son constructeur alors qu'une classe sans classe de base explicite le peut.
    Une structure ne peut pas étendre une autre classe, une classe peut.
    Une structure ne peut pas déclarer des membres protégés (par exemple, des champs, des types imbriqués) qu'une classe peut.
    Une structure ne peut pas déclarer les membres d'une fonction abstraite, une classe abstraite le peut.
    Une structure ne peut pas déclarer les membres d'une fonction virtuelle, une classe le peut.
    Une structure ne peut pas déclarer des membres de fonction scellés, une classe le peut.
    Une structure ne peut pas déclarer les membres d'une fonction de substitution, une classe le peut.
    La seule exception à cette règle est qu'une structure peut remplacer les méthodes virtuelles de System.Object, viz, Equals () et GetHashCode () et ToString ().

Zain Ali
la source
Dans quelles circonstances utiliserait-on un événement avec une structure? Je peux imaginer qu'un programme très soigneusement écrit pourrait utiliser des événements avec une structure d'une manière qui fonctionnerait, mais seulement si la structure n'a jamais été copiée ou passée par valeur, auquel cas il pourrait aussi bien s'agir d'une classe.
supercat
@supercat Oui, un événement non statique dans une structure serait très étrange, et il ne sera utile que pour les structures mutables, et l'événement lui-même (s'il s'agit d'un événement "de type champ") transforme la structure en "mutable" "catégorie et introduit également un champ de type référence dans la structure. Les événements non statiques dans les structures doivent être mauvais.
Jeppe Stig Nielsen
@JeppeStigNielsen: Le seul modèle que je pouvais voir où il serait logique qu'un struct ait un événement serait si le but de la struct était de contenir une référence immuable à un objet de classe pour lequel il se comportait comme un proxy. Les événements automatiques seraient cependant totalement inutiles dans un tel scénario; au lieu de cela, les événements d'abonnement et de désabonnement devraient être relayés à la classe derrière la structure. Je souhaite que .NET ait (ou permettrait de définir) un type de structure "cache-box" avec un champ de type caché initialement nul Object, qui contiendrait une référence à une copie encadrée de la structure.
supercat
1
@JeppeStigNielsen: Structs surclassent les classes dans de nombreux scénarios d'utilisation de proxy; le plus gros problème avec l'utilisation de structures est que dans les cas où la boxe finit par être nécessaire, elle finit souvent par être reportée dans une boucle interne. S'il y avait un moyen d'éviter que les structures soient encadrées à plusieurs reprises , elles seraient meilleures que les classes dans de nombreux autres scénarios d'utilisation.
supercat
4

Comme mentionné précédemment: les classes sont de type référence tandis que les structures sont des types de valeur avec toutes les conséquences.

Comme un pouce de règle, Framework Design Guidelines recommande d'utiliser Structs au lieu de classes si:

  • Il a une taille d'instance inférieure à 16 octets
  • Il représente logiquement une valeur unique, similaire aux types primitifs (int, double, etc.)
  • C'est immuable
  • Il ne devra pas être fréquemment emballé
kb9
la source
3

Il y a un cas intéressant de casse-tête "classe vs struct" - situation où vous devez renvoyer plusieurs résultats de la méthode: choisissez celui à utiliser. Si vous connaissez l'histoire ValueTuple - vous savez que ValueTuple (struct) a été ajouté car il devrait être plus efficace que Tuple (classe). Mais qu'est-ce que cela signifie en chiffres? Deux tests: l'un est struct / classe qui a 2 champs, l'autre avec struct / classe qui a 8 champs (avec dimension plus que 4 - la classe devrait devenir plus efficace que struct en termes de tics de processeur, mais bien sûr la charge GC devrait également être considérée ).

PS Une autre référence pour le cas spécifique «sturct ou classe avec collections» est là: https://stackoverflow.com/a/45276657/506147

BenchmarkDotNet=v0.10.10, OS=Windows 10 Redstone 2 [1703, Creators Update] (10.0.15063.726)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233540 Hz, Resolution=309.2586 ns, Timer=TSC
.NET Core SDK=2.0.3
  [Host] : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT
  Clr    : .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2115.0
  Core   : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT


            Method |  Job | Runtime |     Mean |     Error |    StdDev |      Min |      Max |   Median | Rank |  Gen 0 | Allocated |
------------------ |----- |-------- |---------:|----------:|----------:|---------:|---------:|---------:|-----:|-------:|----------:|
  TestStructReturn |  Clr |     Clr | 17.57 ns | 0.1960 ns | 0.1834 ns | 17.25 ns | 17.89 ns | 17.55 ns |    4 | 0.0127 |      40 B |
   TestClassReturn |  Clr |     Clr | 21.93 ns | 0.4554 ns | 0.5244 ns | 21.17 ns | 23.26 ns | 21.86 ns |    5 | 0.0229 |      72 B |
 TestStructReturn8 |  Clr |     Clr | 38.99 ns | 0.8302 ns | 1.4097 ns | 37.36 ns | 42.35 ns | 38.50 ns |    8 | 0.0127 |      40 B |
  TestClassReturn8 |  Clr |     Clr | 23.69 ns | 0.5373 ns | 0.6987 ns | 22.70 ns | 25.24 ns | 23.37 ns |    6 | 0.0305 |      96 B |
  TestStructReturn | Core |    Core | 12.28 ns | 0.1882 ns | 0.1760 ns | 11.92 ns | 12.57 ns | 12.30 ns |    1 | 0.0127 |      40 B |
   TestClassReturn | Core |    Core | 15.33 ns | 0.4343 ns | 0.4063 ns | 14.83 ns | 16.44 ns | 15.31 ns |    2 | 0.0229 |      72 B |
 TestStructReturn8 | Core |    Core | 34.11 ns | 0.7089 ns | 1.4954 ns | 31.52 ns | 36.81 ns | 34.03 ns |    7 | 0.0127 |      40 B |
  TestClassReturn8 | Core |    Core | 17.04 ns | 0.2299 ns | 0.2150 ns | 16.68 ns | 17.41 ns | 16.98 ns |    3 | 0.0305 |      96 B |

Test de code:

using System;
using System.Text;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Columns;
using BenchmarkDotNet.Attributes.Exporters;
using BenchmarkDotNet.Attributes.Jobs;
using DashboardCode.Routines.Json;

namespace Benchmark
{
    //[Config(typeof(MyManualConfig))]
    [RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkStructOrClass
    {
        static TestStruct testStruct = new TestStruct();
        static TestClass testClass = new TestClass();
        static TestStruct8 testStruct8 = new TestStruct8();
        static TestClass8 testClass8 = new TestClass8();
        [Benchmark]
        public void TestStructReturn()
        {
            testStruct.TestMethod();
        }

        [Benchmark]
        public void TestClassReturn()
        {
            testClass.TestMethod();
        }


        [Benchmark]
        public void TestStructReturn8()
        {
            testStruct8.TestMethod();
        }

        [Benchmark]
        public void TestClassReturn8()
        {
            testClass8.TestMethod();
        }

        public class TestStruct
        {
            public int Number = 5;
            public struct StructType<T>
            {
                public T Instance;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance;
            }

            private StructType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private StructType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private StructType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private StructType<int> Method4(int i)
            {
                var x = new StructType<int>();
                x.List = new List<string>();
                x.Instance = ++i;
                return x;
            }
        }

        public class TestClass
        {
            public int Number = 5;
            public class ClassType<T>
            {
                public T Instance;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance;
            }

            private ClassType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private ClassType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private ClassType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private ClassType<int> Method4(int i)
            {
                var x = new ClassType<int>();
                x.List = new List<string>();
                x.Instance = ++i;
                return x;
            }
        }

        public class TestStruct8
        {
            public int Number = 5;
            public struct StructType<T>
            {
                public T Instance1;
                public T Instance2;
                public T Instance3;
                public T Instance4;
                public T Instance5;
                public T Instance6;
                public T Instance7;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance1;
            }

            private StructType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private StructType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private StructType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private StructType<int> Method4(int i)
            {
                var x = new StructType<int>();
                x.List = new List<string>();
                x.Instance1 = ++i;
                return x;
            }
        }

        public class TestClass8
        {
            public int Number = 5;
            public class ClassType<T>
            {
                public T Instance1;
                public T Instance2;
                public T Instance3;
                public T Instance4;
                public T Instance5;
                public T Instance6;
                public T Instance7;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance1;
            }

            private ClassType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private ClassType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private ClassType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private ClassType<int> Method4(int i)
            {
                var x = new ClassType<int>();
                x.List = new List<string>();
                x.Instance1 = ++i;
                return x;
            }
        }
    }
}
Roman Pokrovskij
la source
2

Les structures sont la valeur réelle - elles peuvent être vides mais jamais nulles

Cela est vrai, cependant, notez également que depuis .NET 2, les structures prennent en charge une version Nullable et C # fournit du sucre syntaxique pour le rendre plus facile à utiliser.

int? value = null;
value  = 1;
denis phillips
la source
1
Sachez que ce n'est que du sucre syntaxique qui lit 'Nullable <int> value = null;'
Erik van Brakel
@ErikvanBrakel Ce n'est pas seulement du sucre syntaxique. Les différentes règles de boxe signifient (object)(default(int?)) == nullque vous ne pouvez pas faire avec un autre type de valeur, car il y a plus que du sucre ici. Le seul sucre est int?pour Nullable<int>.
Jon Hanna
-1; cela ne résout pas la question de la différence entre les structures et les classes, et en tant que tel, cela aurait dû être un commentaire sur la réponse à laquelle vous répondez, pas une réponse distincte. (Même si les normes du site étaient peut-être différentes en août 2008!)
Mark Amery
1

Chaque variable ou champ d'un type de valeur primitif ou d'un type de structure contient une instance unique de ce type, y compris tous ses champs (publics et privés). En revanche, des variables ou des champs de types de référence peuvent être nuls ou faire référence à un objet, stocké ailleurs, sur lequel un certain nombre d'autres références peuvent également exister. Les champs d'une structure seront stockés au même endroit que la variable ou le champ de ce type de structure, qui peut être soit sur la pile, soit faire partie d' un autre objet tas.

La création d'une variable ou d'un champ d'un type de valeur primitif le créera avec une valeur par défaut; la création d'une variable ou d'un champ d'un type de structure créera une nouvelle instance, en créant tous les champs de la manière par défaut. La création d'une nouvelle instance d'un type de référence commence par la création par défaut de tous les champs, puis par l'exécution d'un code supplémentaire facultatif en fonction du type.

La copie d'une variable ou d'un champ d'un type primitif dans une autre copiera la valeur. La copie d'une variable ou d'un champ de type de structure vers une autre copiera tous les champs (publics et privés) de l'ancienne instance vers la dernière instance. La copie d'une variable ou d'un champ de type référence dans une autre fera que cette dernière se référera à la même instance que la première (le cas échéant).

Il est important de noter que dans certains langages comme C ++, le comportement sémantique d'un type est indépendant de la façon dont il est stocké, mais ce n'est pas le cas de .NET. Si un type implémente une sémantique de valeur mutable, la copie d'une variable de ce type dans une autre copie les propriétés de la première vers une autre instance, référencée par la seconde, et l'utilisation d'un membre de la seconde pour muter entraînera la modification de cette seconde instance. , mais pas le premier. Si un type implémente une sémantique de référence mutable, la copie d'une variable dans une autre et l'utilisation d'un membre de la seconde pour muter l'objet affecteront l'objet référencé par la première variable; les types avec une sémantique immuable ne permettent pas la mutation, donc peu importe sémantiquement si la copie crée une nouvelle instance ou crée une autre référence à la première.

Dans .NET, il est possible pour les types de valeur d'implémenter l'une des sémantiques ci-dessus, à condition que tous leurs champs puissent faire de même. Un type de référence, cependant, ne peut implémenter que la sémantique de référence mutable ou la sémantique immuable; les types de valeur avec des champs de types de référence mutables sont limités à l'implémentation d'une sémantique de référence mutable ou d'une sémantique hybride étrange.

supercat
la source