web-dev-qa-db-fra.com

Que fait le mot-clé "new" pour une structure en C #?

En C #, les structures sont gérées en termes de valeurs et les objets sont en référence. D'après ma compréhension, lors de la création d'une instance d'une classe, le mot clé new oblige C # à utiliser les informations de classe pour créer l'instance, comme ci-dessous:

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

Pour struct, vous ne créez pas un objet, mais définissez simplement une variable sur 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 déclarer des variables par MyStruct ms = new MyStruct(), que fait le mot clé new ici à l'instruction? . Si struct ne peut pas être un objet, quelle est l'instanciation de new ici?

63
KMC

De struct (C# Reference) sur MSDN:

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

À ma connaissance, vous ne pourrez pas utiliser une structure correctement sans utiliser nouveau à moins de vous assurer d'initialiser tous les champs manuellement. Si vous utilisez le nouvel opérateur, le constructeur le fera pour vous.

J'espère que ça clarifie les choses. 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é en ajouter un peu plus ici. Je pense que la meilleure façon de le comprendre est de l'essayer. Créez un projet de 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. Supprimez les constructeurs et voyez ce qui se passe. Essayez d'utiliser un constructeur qui n'initialise qu'une variable (j'en ai commenté une ... elle ne compilera pas). Essayez avec et sans le mot-clé nouveau (j'ai commenté quelques exemples, décommentez-les et essayez-les).

56
joshhendo

Catch excellente réponse d'Eric Lippert de ce fil. Pour le citer:

Lorsque vous "nouveau" un type de valeur, trois choses se produisent. Tout d'abord, le gestionnaire de mémoire alloue de l'espace à partir du stockage à court terme. Deuxièmement, le constructeur reçoit une référence à l'emplacement de stockage à court terme. Après l'exécution du constructeur, la valeur qui se trouvait dans l'emplacement de stockage à court terme est copiée vers l'emplacement de stockage de la valeur, où qu'elle se trouve. N'oubliez pas que les variables de type valeur stockent la valeur réelle.

(Notez que le compilateur est autorisé à optimiser ces trois étapes en une seule étape si le compilateur peut déterminer que cela n'expose jamais une structure partiellement construite au code utilisateur. Autrement dit, le compilateur peut générer du code qui transmet simplement une référence à la finale emplacement de stockage au constructeur, ce qui permet d'économiser une allocation et une copie.)

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

16
nawfal

L'utilisation de "new MyStuct ()" garantit que tous les champs sont définis sur une certaine valeur. Dans le cas ci-dessus, rien n'est différent. Si, au lieu de définir ms.name, où vous essayez de le lire, vous obtiendrez une erreur "Utilisation du champ" nom "possible non attribué" dans VS.

3
Daryl

Chaque fois qu'un objet ou une structure prend vie, tous ses champs existent également; si l'un de ces champs est de type struct, tous les champs imbriqués existent également. Lorsqu'un tableau est créé, tous ses éléments viennent à l'existence (et, comme ci-dessus, si l'un de ces éléments sont des structures, les champs de ces structures existent également). Tout cela se produit avant qu'un code constructeur ait une chance de s'exécuter.

Dans .net, un constructeur struct n'est en fait rien de plus qu'une méthode qui prend une struct comme paramètre 'out'. En C #, une expression qui appelle un constructeur struct allouera une instance de structure temporaire, appellera le constructeur à ce sujet, puis utilisera 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) dans vb.net effacera myStruct avant l'exécution de la première instruction du constructeur; dans le constructeur, toutes les écritures sur l'objet en cours de construction fonctionneront immédiatement sur myStruct.

3
supercat

Dans une structure, le mot clé new est inutilement déroutant. Ça ne fait rien. C'est juste requis si vous voulez utiliser le constructeur. Il ne pas effectue un new.

La signification habituelle de new est d'allouer du stockage permanent (sur le tas.) Un langage comme C++ autorise new myObject() ou simplement myObject(). Les deux appellent le même constructeur. Mais le premier crée un nouvel objet et renvoie un pointeur. Ce dernier ne fait que créer un temp. Toute structure ou classe peut utiliser l'une ou l'autre. new est un choix, et cela signifie quelque chose.

C # ne vous donne pas le choix. Les classes sont toujours dans le tas et les structures sont toujours sur la pile. Il n'est pas possible d'effectuer un vrai new sur une structure. Les programmeurs C # expérimentés sont habitués à cela. Quand ils voient ms = new MyStruct(); ils savent ignorer new comme juste syntaxe. Ils savent que cela agit comme ms = MyStruct(), qui ne fait qu'assigner à un objet existant.

Curieusement (?), Les classes nécessitent le new. c=myClass(); n'est pas autorisé (en utilisant le constructeur pour définir les valeurs de l'objet existant c.) Vous devez créer quelque chose comme c.init();. Donc, vous n'avez vraiment jamais le choix - les constructeurs allouent toujours pour les classes et jamais pour les structures. Le new est toujours juste de la décoration.

Je suppose que la raison pour laquelle il faut des faux new dans les structures est pour que vous puissiez facilement changer une structure en classe (en supposant que vous utilisez toujours myStruct=new myStruct(); lorsque vous déclarez pour la première fois, ce qui est recommandé.)

1
Owen Reynolds

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.

Ici, nous avons 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é des méthodes de la classe de test serait

  • 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 voudrez peut-être jeter un œil à OpCodes.Initobj et OpCodes.Newobj . Le type de valeur est généralement initialisé avec OpCodes.Initobj , mais comme MSDN le dit OpCodes.Newobj serait également utilisé.

  • description dans OpCodes.Newobj

    Les types de valeurs ( ne sont généralement pas créés à l'aide de newobj. Ils sont généralement alloués sous forme d'arguments ou de variables locales, à l'aide de newarr (pour les tableaux à base zéro et unidimensionnels), ou sous forme de champs d'objets. Une fois alloués, ils sont initialisés à l'aide d'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 passée en argument, stockée dans un local, etc.

Pour chaque type de valeur 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 lui-même est déclaré classe abstraite

    Autrement dit, vous ne pouvez pas nouveau directement.

  2. structs ne peut pas contenir de constructeurs explicites sans paramètre

    Autrement dit, lorsque vous nouveau un struct, vous tomberiez dans le cas ci-dessus de NewMyLong ou NewMyLongWithValue.

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

0
Ken Kin