¿Qué le hace la palabra clave “nuevo” a una estructura en C #?

En C #, las estructuras se gestionan en términos de valores y los objetos están en referencia. Desde mi entendimiento, al crear una instancia de una clase, la palabra clave new hace que C # use la información de la clase para crear la instancia, como se muestra a continuación:

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

Para struct, no estás creando un objeto sino simplemente configurando una variable a un valor:

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

Lo que no entiendo es si declarar variables por MyStruct ms = new MyStruct() , ¿cuál es la palabra clave new aquí está haciendo a la statement? . Si struct no puede ser un objeto, ¿cuál es la new instancia de aquí?

Desde struct (C# Reference) en MSDN:

Cuando crea un objeto struct usando el nuevo operador, se crea y se llama al constructor apropiado. A diferencia de las clases, las estructuras se pueden crear instancias sin usar el nuevo operador. Si no usa nuevo, los campos permanecerán sin asignar y el objeto no podrá utilizarse hasta que se hayan inicializado todos los campos.

A mi entender, en realidad no podrás usar una estructura correctamente sin usar nueva a menos que te asegures de inicializar todos los campos manualmente. Si usa el nuevo operador, el constructor hará esto por usted.

Espero que eso lo aclare. Si necesita una aclaración sobre esto, hágamelo saber.


Editar

Hay un hilo de comentarios bastante largo, así que pensé en agregar un poco más aquí. Creo que la mejor manera de entenderlo es intentarlo. Haga un proyecto de consola en Visual Studio llamado “StructTest” y copie el siguiente código en él.

 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); } } } 

Juega con eso. Eliminar los constructores y ver qué pasa. Intenta usar un constructor que solo inicialice una variable (he comentado una … no se comstackrá). Pruebe con y sin la nueva palabra clave (he comentado algunos ejemplos, los eliminó y los puso a prueba).

Captura la excelente respuesta de Eric Lippert de este hilo. Para citarlo:

Cuando “nuevo” un tipo de valor, suceden tres cosas. Primero, el administrador de memoria asigna espacio desde el almacenamiento a corto plazo. En segundo lugar, al constructor se le pasa una referencia a la ubicación de almacenamiento a corto plazo. Después de que se ejecuta el constructor, el valor que estaba en la ubicación de almacenamiento a corto plazo se copia en la ubicación de almacenamiento para el valor, donde sea que esté. Recuerde, las variables del tipo de valor almacenan el valor real.

(Tenga en cuenta que el comstackdor puede optimizar estos tres pasos en un solo paso si el comstackdor puede determinar que al hacerlo nunca expone una estructura construida parcialmente al código de usuario. Es decir, el comstackdor puede generar código que simplemente pasa una referencia al final ubicación de almacenamiento para el constructor, con lo que se guarda una asignación y una copia).

( Haciendo esta respuesta ya que realmente es uno )

El uso de “nuevo MyStuct ()” garantiza que todos los campos estén configurados en algún valor. En el caso anterior, nada es diferente. Si en lugar de establecer ms.name trataras de leerlo, obtendrías un error de “Uso del posible campo no asignado ‘nombre'” en VS.

Cada vez que un objeto o estructura aparece, todos sus campos también aparecen; si alguno de esos campos son tipos de estructuras, todos los campos nesteds también existen. Cuando se crea una matriz, todos sus elementos entran en existencia (y, como anteriormente, si alguno de esos elementos son estructuras, los campos de esas estructuras también entran en existencia). Todo esto ocurre antes de que cualquier código de constructor tenga la oportunidad de ejecutarse.

En .net, un constructor de estructuras no es más que un método que toma una estructura como un parámetro ‘fuera’. En C #, una expresión que llama a un constructor de estructura asignará una instancia de estructura temporal, llamará al constructor sobre eso y luego usará esa instancia temporal como el valor de la expresión. Tenga en cuenta que esto es diferente de vb.net, donde el código generado para un constructor comenzará poniendo a cero todos los campos, pero donde el código de la persona que llama intentará que el constructor opere directamente sobre el destino. Por ejemplo: myStruct = new myStructType(whatever) en vb.net borrará myStruct antes de que se myStruct la primera instrucción del constructor; dentro del constructor, cualquier escritura en el objeto en construcción funcionará inmediatamente en myStruct .

ValueType y las estructuras son algo especial en C #. Aquí te muestro lo que sucede cuando tienes algo nuevo .

Aquí tenemos lo siguiente

  • Código

     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); 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); } public ThatLong() { int i=0; var b=i is ValueType; } uint m_Low; int m_Hi; } 

Y el IL generado de los métodos de la clase de prueba sería

  • ILLINOIS

     // 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.si // 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.si // 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 } 

El comportamiento de los métodos se comentan en el código IL. Y es posible que desee echar un vistazo a OpCodes.Initobj y OpCodes.Newobj . El tipo de valor generalmente se inicializa con OpCodes.Initobj , pero como MSDN dice OpCodes.Newobj también se utilizará.

  • descripción en OpCodes.Newobj

    Los tipos de valores generalmente no se crean con newobj. Por lo general, se asignan como argumentos o variables locales, utilizando newarr (para matrices unidimensionales o de base cero) o como campos de objetos. Una vez asignados, se inicializan usando Initobj. Sin embargo , la instrucción newobj se puede usar para crear una nueva instancia de un tipo de valor en la stack, que luego se puede pasar como un argumento, almacenarse en un local, y así sucesivamente.

Para cada tipo de valor que sea numérico, de byte a double , tiene un código de operación definido. Aunque se declaran como struct , hay alguna diferencia en el IL generado como se muestra.

Aquí hay dos cosas más que mencionar:

  1. ValueType sí mismo se declara una clase abstracta

    Es decir, no puedes hacerlo nuevo directamente.

  2. struct s no puede contener constructores explícitos sin parámetros

    Es decir, cuando NewMyLong una struct nueva , podría caer en el caso anterior de NewMyLong o NewMyLongWithValue .

Para resumir , lo nuevo para los tipos y estructuras de valor es la consistencia del concepto de lenguaje.

En una estructura, la new palabra clave es innecesariamente confusa. No hace nada. Solo se requiere si quieres usar el constructor. No realiza una new .

El significado usual de new es asignar almacenamiento permanente (en el montón). Un lenguaje como C ++ permite new myObject() o simplemente myObject() . Ambos llaman al mismo constructor. Pero el primero crea un nuevo objeto y devuelve un puntero. Este último simplemente crea una temperatura. Cualquier estructura o clase puede usar cualquiera. new es una elección, y significa algo.

C # no te da una opción. Las clases siempre están en el montón, y las estructuras siempre están en la stack. No es posible realizar un trabajo realmente new en una estructura. Los progtwigdores experimentados de C # están acostumbrados a esto. Cuando ven ms = new MyStruct(); saben ignorar lo new como una simple syntax. Saben que actúa como ms = MyStruct() , que simplemente asigna a un objeto existente.

Curiosamente (?), Las clases requieren lo new . c=myClass(); no está permitido (usar el constructor para establecer valores del objeto existente c .) Tendría que hacer algo como c.init(); . Por lo tanto, nunca tiene una opción: los constructores siempre asignan para las clases y nunca para las estructuras. Lo new siempre es solo decoración.

Supongo que la razón para requerir new estructuras falsas es para que pueda cambiar fácilmente una estructura en una clase (suponiendo que siempre use myStruct=new myStruct(); cuando declare por primera vez, lo cual se recomienda).