76 votes

Que fait le mot clé "new" à un struct en C# ?

En C#, les Structs sont gérés en termes de valeurs, et les objets sont en référence. D'après ce que j'ai compris, lors de la création d'une instance d'une classe, le mot-clé new permet à C# d'utiliser les informations de la classe pour créer l'instance, comme dans l'exemple ci-dessous :

class MyClass
{
    ...
}
MyClass mc = new MyClass();

Pour la structure, vous ne créez pas d'objet, mais définissez simplement une variable à une valeur :

struct MyStruct
{
    public string name;
}
MyStruct ms;
//MyStruct ms = new MyStruct();     
ms.name = "donkey";

Ce que je ne comprends pas, c'est que si l'on déclare des variables par MyStruct ms = new MyStruct() quel est le mot-clé new ici fait à la déclaration ? . Si struct ne peut pas être un objet, quel est le new ici instancié ?

3 votes

Une instance d'un struct est un objet. La distinction que vous comprenez probablement mal est celle entre les types de valeur et les types de référence.

0 votes

Mais en C il n'y a pas d'objet et la structure n'est pas un objet. Donc en C# struct est implémenté comme un objet ?

1 votes

Penser à C# en termes de C n'est pas utile. Ignorez les différences syntaxiques, ce sont des langages complètement différents.

68voto

joshhendo Points 1042

De struct (C# Reference) sur MSDN :

Lorsque vous créez un objet struct à l'aide de l'opérateur new, il est créé et le constructeur approprié est appelé. Contrairement aux classes, les structs peuvent être instanciés sans utiliser l'opérateur new. Si vous n'utilisez pas new, les champs resteront non assignés et l'objet ne pourra pas être utilisé tant que tous les champs n'auront pas été initialisés.

D'après ce que j'ai compris, vous ne serez pas en mesure d'utiliser correctement un struct sans utiliser nouveau à moins que vous ne vous assuriez d'initialiser tous les champs manuellement. Si vous utilisez l'opérateur new, alors un constructeur correctement écrit a la possibilité de le faire pour vous.

J'espère que c'est clair. Si vous avez besoin d'éclaircissements à ce sujet, faites-le moi savoir.


Modifier

Il y a un long fil de commentaires, alors j'ai pensé ajouter un peu plus ici. Je pense que la meilleure façon de le comprendre est de l'essayer. Créez un projet console dans Visual Studio appelé "StructTest" et copiez-y le code suivant.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace struct_test
{
    class Program
    {
        public struct Point
        {
            public int x, y;

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

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

            // It will break with this constructor. If uncommenting this one
            // comment out the other one with only one integer, otherwise it
            // will fail because you are overloading with duplicate parameter
            // types, rather than what I'm trying to demonstrate.
            /*public Point(int y)
            {
                this.y = y;
            }*/
        }

        static void Main(string[] args)
        {
            // Declare an object:
            Point myPoint;
            //Point myPoint = new Point(10, 20);
            //Point myPoint = new Point(15);
            //Point myPoint = new Point();

            // Initialize:
            // Try not using any constructor but comment out one of these
            // and see what happens. (It should fail when you compile it)
            myPoint.x = 10;
            myPoint.y = 20;

            // Display results:
            Console.WriteLine("My Point:");
            Console.WriteLine("x = {0}, y = {1}", myPoint.x, myPoint.y);

            Console.ReadKey(true);
        }
    }
}

Jouez avec. Enlevez les constructeurs et voyez ce qui se passe. Essayez d'utiliser un constructeur qui n'initialise qu'une variable (j'en ai commenté un... il ne compile pas). Essayez avec et sans l'option nouveau (J'ai commenté quelques exemples, décommentez-les et essayez-les).

0 votes

"les structs peuvent être instanciés" ? mais le struct ne peut pas être un objet, n'est-ce pas ? Les "champs" dans le struct sont les propriétés et les méthodes - si le struct n'est pas un objet, pourquoi ses champs doivent-ils être initialisés ? Je pense que j'ai besoin de plus de clarification. merci.

0 votes

Pourquoi ne serait pas les champs de la structure doivent être initialisés, si vous n'appelez pas un constructeur ? Si vous ne les initialisez pas, et que vous n'appelez pas un constructeur pour les initialiser, ils restent non initialisés.

0 votes

Un struct est une sorte d'objet. Décrit sur le site Web de Microsoft comme un "objet léger". Il peut avoir des variables, mais pas de fonctions. Un struct est en quelque sorte une classe, mais tous ses membres sont publics et il ne peut pas avoir de fonctions. Elle vous permet de stocker des informations, mais vous ne pouvez pas manipuler ou contrôler ces informations comme vous le faites dans une classe. Vous pouvez créer une "nouvelle" structure pour utiliser la même variable mais effacer toutes les données.

21voto

nawfal Points 13500

Attrapez L'excellente réponse d'Eric Lippert dans ce fil. Pour le citer :

Lorsque vous "remplacez" un type de valeur, trois choses se produisent. Premièrement, le gestionnaire de mémoire alloue de l'espace à partir du stockage à court terme. Deuxièmement, le constructeur constructeur se voit passer une référence à l'emplacement de stockage à court terme. Après l'exécution du constructeur, la valeur qui se trouvait dans le stockage à court terme est copiée dans le stockage à long terme. est copiée dans l'emplacement de stockage de la valeur, où qu'il se trouve. Rappelez-vous que les variables de type valeur stockent la valeur réelle.

(Notez que le compilateur peut optimiser ces trois étapes en une seule si une seule étape s'il peut déterminer que cela n'expose pas une structure partiellement struct partiellement construite au code utilisateur. C'est-à-dire que le compilateur peut générer un code qui passe simplement une référence à l'emplacement de au constructeur, ce qui permet d'économiser une allocation et une copie. de copie).

( Faire cette réponse car c'en est vraiment une )

0 votes

Il est intéressant de noter que parce que out sont un concept C#, plutôt qu'un concept utilisé par le moteur d'exécution .NET, qui consiste à transmettre une structure partiellement construite en tant que paramètre out à une méthode externe exposera ses valeurs au code externe, même si le compilateur C# suppose qu'il ne le fera pas. On peut, par exemple, définir une structure de manière à ce que myThing = newmyThing(5); initialisera un champ de myThing tout en laissant les autres inchangés.

3 votes

Je pense que les différents groupes linguistiques ont probablement leur propre vision de ce que devrait être .NET, et prétendent que cela correspond à leur vision. Par exemple, le groupe C# pense probablement que .NET devrait ont un caractère exécutoire out paramètres, et si tout le monde programmait en C#, il le ferait, mais une méthode virtuelle avec une out sera considéré par les autres langages comme une méthode virtuelle avec un paramètre ref paramètre. Dans certains cas, il est bon que les langages ne soient pas limités au sous-ensemble minimal de fonctionnalités que les autres implémenteurs de langages peuvent vouloir mettre en œuvre, mais il y a aussi des dangers.

1 votes

Ceci devrait être la réponse

3voto

Daryl Points 755

L'utilisation de "new MyStuct()" garantit que tous les champs sont définis sur une valeur donnée. Dans le cas ci-dessus, rien n'est différent. Si, au lieu de définir ms.name, vous essayiez de le lire, vous obtiendriez une erreur "Use of possible unassigned field 'name'" dans VS.

3voto

supercat Points 25534

Chaque fois qu'un objet ou un struct existe, tous ses champs existent également ; si l'un de ces champs est un type struct, tous les champs imbriqués existent également. Lorsqu'un tableau est créé, tous ses éléments existent (et, comme ci-dessus, si certains de ces éléments sont des structures, les champs de ces structures existent également). Tout cela se produit avant que le code du constructeur n'ait la possibilité de s'exécuter.

En .net, un constructeur de structure n'est en fait rien de plus qu'une méthode qui prend une structure comme paramètre de sortie. En C#, une expression qui appelle un constructeur de structure alloue une instance temporaire de structure, appelle le constructeur sur celle-ci, puis utilise cette instance temporaire comme valeur de l'expression. Notez que ceci est différent de vb.net, où le code généré pour un constructeur commencera par mettre à zéro tous les champs, mais où le code de l'appelant tentera de faire fonctionner le constructeur directement sur la destination. Par exemple : myStruct = new myStructType(whatever) en vb.net va effacer myStruct avant que la première instruction du constructeur ne s'exécute ; à l'intérieur du constructeur, toute écriture dans l'objet en construction sera immédiatement exécutée sur myStruct .

0voto

Ken Kin Points 1604

ValueType et les structures sont quelque chose de spécial en C#. Ici, je vous montre ce qui se passe lorsque vous nouveau quelque chose.

Nous avons ici les éléments suivants

  • Code

    partial class TestClass {
        public static void NewLong() {
            var i=new long();
        }
    
        public static void NewMyLong() {
            var i=new MyLong();
        }
    
        public static void NewMyLongWithValue() {
            var i=new MyLong(1234);
        }
    
        public static void NewThatLong() {
            var i=new ThatLong();
        }
    }
    
    [StructLayout(LayoutKind.Sequential)]
    public partial struct MyLong {
        const int bits=8*sizeof(int);
    
        public static implicit operator int(MyLong x) {
            return (int)x.m_Low;
        }
    
        public static implicit operator long(MyLong x) {
            long y=x.m_Hi;
            return (y<<bits)|x.m_Low;
        }
    
        public static implicit operator MyLong(long x) {
            var y=default(MyLong);
            y.m_Low=(uint)x;
            y.m_Hi=(int)(x>>bits);
            return y;
        }
    
        public MyLong(long x) {
            this=x;
        }
    
        uint m_Low;
        int m_Hi;
    }
    
    public partial class ThatLong {
        const int bits=8*sizeof(int);
    
        public static implicit operator int(ThatLong x) {
            return (int)x.m_Low;
        }
    
        public static implicit operator long(ThatLong x) {
            long y=x.m_Hi;
            return (y<<bits)|x.m_Low;
        }
    
        public static implicit operator ThatLong(long x) {
            return new ThatLong(x);
        }
    
        public ThatLong(long x) {
            this.m_Low=(uint)x;
            this.m_Hi=(int)(x>>bits);
        }
    
        public ThatLong() {
            int i=0;
            var b=i is ValueType;
        }
    
        uint m_Low;
        int m_Hi;
    }

Et l'IL générée des méthodes de la classe de test serait la suivante

  • IL

    // NewLong
    .method public hidebysig static 
        void NewLong () cil managed 
    {
        .maxstack 1
        .locals init (
            [0] int64 i
        )
    
        IL_0000: nop
        IL_0001: ldc.i4.0 // push 0 as int
        IL_0002: conv.i8  // convert the pushed value to long
        IL_0003: stloc.0  // pop it to the first local variable, that is, i
        IL_0004: ret
    } 
    
    // NewMyLong
    .method public hidebysig static 
        void NewMyLong () cil managed 
    {
        .maxstack 1
        .locals init (
            [0] valuetype MyLong i
        )
    
        IL_0000: nop
        IL_0001: ldloca.s i     // push address of i
        IL_0003: initobj MyLong // pop address of i and initialze as MyLong
        IL_0009: ret
    } 
    
    // NewMyLongWithValue 
    .method public hidebysig static 
        void NewMyLongWithValue () cil managed 
    {
        .maxstack 2
        .locals init (
            [0] valuetype MyLong i
        )
    
        IL_0000: nop
        IL_0001: ldloca.s i  // push address of i
        IL_0003: ldc.i4 1234 // push 1234 as int
        IL_0008: conv.i8     // convert the pushed value to long
    
        // call the constructor
        IL_0009: call instance void MyLong::.ctor(int64) 
    
        IL_000e: nop
        IL_000f: ret
    } 
    
    // NewThatLong
    .method public hidebysig static 
        void NewThatLong () cil managed 
    {
        // Method begins at RVA 0x33c8
        // Code size 8 (0x8)
        .maxstack 1
        .locals init (
            [0] class ThatLong i
        )
    
        IL_0000: nop
    
        // new by calling the constructor and push it's reference
        IL_0001: newobj instance void ThatLong::.ctor() 
    
        // pop it to the first local variable, that is, i
        IL_0006: stloc.0
    
        IL_0007: ret
    } 

Le comportement des méthodes est commenté dans le code IL. Et vous pouvez jeter un coup d'œil à OpCodes.Initobj et OpCodes.Newobj . Le type de valeur est généralement initialisé avec OpCodes.Initobj mais comme le dit MSDN OpCodes.Newobj serait également utilisé.

  • description en OpCodes.Newobj

    Les types de valeurs sont pas habituellement créés à l'aide de newobj. Ils sont généralement alloués soit comme arguments ou variables locales, à l'aide de newarr (pour les tableaux unidimensionnels basés sur zéro), soit comme champs d'objets. Une fois alloués, ils sont initialisés à l'aide de Initobj. Cependant L'instruction newobj peut être utilisée pour créer une nouvelle instance d'un type de valeur sur la pile, qui peut ensuite être transmise en tant qu'argument, stockée dans un local, et ainsi de suite.

Pour chaque type de valeur qui est numérique, de byte à double a un op-code défini. Bien qu'ils soient déclarés comme struct il y a une certaine différence dans l'IL généré comme indiqué.

Voici deux autres choses à mentionner :

  1. ValueType est lui-même déclaré comme un classe abstraite

    C'est-à-dire que vous ne pouvez pas nouveau directement.

  2. struct ne peuvent pas contenir de constructeurs explicites sans paramètres

    C'est-à-dire, quand vous nouveau a struct vous vous retrouveriez dans le cas ci-dessus, soit NewMyLong ou NewMyLongWithValue .

Pour résumer , nouveau pour les types de valeurs et les structures sont pour la cohérence du concept de la langue.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X