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?
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).
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 )
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.
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
.
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é.)
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:
ValueType
lui-même est déclaré classe abstraite
Autrement dit, vous ne pouvez pas nouveau directement.
struct
s 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.