c# - example - ¿Cuándo usar struct?




struct c# example (19)

.NET admite value types y value types reference types (en Java, solo puede definir tipos de referencia). Las instancias de reference types de reference types se asignan en el montón administrado y se recolectan cuando no hay referencias pendientes a ellas. Las instancias de value types de value types , por otro lado, se asignan en la stack y, por lo tanto, la memoria asignada se reclama tan pronto como finaliza su alcance. Y, por supuesto, los value types se pasan por valor y los reference types de referencia por referencia. Todos los tipos de datos primitivos de C #, excepto System.String, son tipos de valor.

Cuando usar struct sobre clase,

En C #, las structs son value types , las clases son reference types . Puede crear tipos de valor, en C #, utilizando la palabra clave enum y la palabra clave struct . El uso de un value type lugar de un reference type dará como resultado menos objetos en el montón administrado, lo que se traduce en una menor carga en el recolector de basura (GC), ciclos de GC menos frecuentes y, en consecuencia, un mejor rendimiento. Sin embargo, los value types tienen sus desventajas. Transmitir una struct grande es definitivamente más costoso que pasar una referencia, ese es un problema obvio. El otro problema es la sobrecarga asociada con el boxing/unboxing . En caso de que se esté preguntando qué significa boxing/unboxing , siga estos enlaces para obtener una buena explicación sobre el boxing y el unboxing . Aparte del rendimiento, hay ocasiones en las que simplemente necesita que los tipos tengan semántica de valor, lo que sería muy difícil (o feo) de implementar si los reference types son todo lo que tiene. Debe usar solo value types , cuando necesite copiar semántica o necesite inicialización automática, normalmente en arrays de estos tipos.

¿Cuándo debes usar struct y no class en C #? Mi modelo conceptual es que las estructuras se utilizan en momentos en que el elemento es simplemente una colección de tipos de valor . Una forma de mantenerlos lógicamente a todos juntos en un todo cohesivo.

Me encontré con estas reglas here :

  • Una estructura debe representar un solo valor.
  • Una estructura debe tener una huella de memoria de menos de 16 bytes.
  • Una estructura no debe ser cambiada después de la creación.

¿Funcionan estas reglas? ¿Qué significa una estructura semánticamente?


Además de la respuesta "es un valor", un escenario específico para el uso de estructuras es cuando usted sabe que tiene un conjunto de datos que está causando problemas de recolección de basura, y tiene muchos objetos. Por ejemplo, una gran lista / matriz de instancias de persona. La metáfora natural aquí es una clase, pero si tiene un gran número de instancias de Persona de larga vida, pueden terminar obstruyendo GEN-2 y causando atascos de GC. Si el escenario lo justifica, un posible enfoque aquí es usar una matriz (no lista) de Estructuras Personales, es decir, Person[] . Ahora, en lugar de tener millones de objetos en GEN-2, tiene un solo fragmento en el LOH (supongo que aquí no hay cadenas, etc., es decir, un valor puro sin ninguna referencia). Esto tiene muy poco impacto GC.

Trabajar con estos datos es incómodo, ya que es probable que los datos tengan un tamaño excesivo para una estructura y no quiera copiar valores de grasa todo el tiempo. Sin embargo, acceder a él directamente en una matriz no copia la estructura; está en su lugar (en contraste con un indexador de lista, que sí copia). Esto significa mucho trabajo con índices:

int index = ...
int id = peopleArray[index].Id;

Tenga en cuenta que mantener los valores en sí mismos inmutables ayudará aquí. Para una lógica más compleja, use un método con un parámetro by-ref:

void Foo(ref Person person) {...}
...
Foo(ref peopleArray[index]);

Nuevamente, esto está en el lugar, no hemos copiado el valor.

En escenarios muy específicos, esta táctica puede ser muy exitosa; Sin embargo, es un scernario bastante avanzado que debe intentarse solo si sabes lo que estás haciendo y por qué. El valor predeterminado aquí sería una clase.


Con la excepción de los tipos de valor que son utilizados directamente por el tiempo de ejecución y varios otros para propósitos de PInvoke, solo debe usar los tipos de valor en 2 escenarios.

  1. Cuando necesitas copiar la semántica.
  2. Cuando necesita inicialización automática, normalmente en matrices de estos tipos.

De la especificación del lenguaje C # :

1.7 Estructuras

Al igual que las clases, las estructuras son estructuras de datos que pueden contener miembros de datos y miembros de funciones, pero a diferencia de las clases, las estructuras son tipos de valor y no requieren asignación de pila. Una variable de un tipo de estructura almacena directamente los datos de la estructura, mientras que una variable de un tipo de clase almacena una referencia a un objeto asignado dinámicamente. Los tipos de estructura no admiten la herencia especificada por el usuario, y todos los tipos de estructura heredan implícitamente del tipo objeto.

Las estructuras son particularmente útiles para estructuras de datos pequeños que tienen valores semánticos. Los números complejos, los puntos en un sistema de coordenadas o los pares clave-valor en un diccionario son buenos ejemplos de estructuras. El uso de estructuras en lugar de clases para estructuras de datos pequeñas puede hacer una gran diferencia en el número de asignaciones de memoria que realiza una aplicación. Por ejemplo, el siguiente programa crea e inicializa una matriz de 100 puntos. Con el punto implementado como una clase, se crean instancias de 101 objetos separados, uno para la matriz y uno para cada uno de los 100 elementos.

class Point
{
   public int x, y;

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

class Test
{
   static void Main() {
      Point[] points = new Point[100];
      for (int i = 0; i < 100; i++) points[i] = new Point(i, i);
   }
}

Una alternativa es hacer de Point una estructura.

struct Point
{
   public int x, y;

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

Ahora, solo se crea una instancia de un objeto, el de la matriz, y las instancias de Point se almacenan en línea en la matriz.

Los constructores Struct se invocan con el nuevo operador, pero eso no implica que se esté asignando memoria. En lugar de asignar dinámicamente un objeto y devolverle una referencia, un constructor de estructura simplemente devuelve el propio valor de estructura (normalmente en una ubicación temporal en la pila), y este valor se copia según sea necesario.

Con las clases, es posible que dos variables hagan referencia al mismo objeto y, por lo tanto, es posible que las operaciones en una variable afecten al objeto al que hace referencia la otra variable. Con las estructuras, cada una de las variables tiene su propia copia de los datos, y no es posible que las operaciones en una afecten a la otra. Por ejemplo, la salida producida por el siguiente fragmento de código depende de si Point es una clase o una estructura.

Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);

Si Point es una clase, la salida es 20 porque ayb hacen referencia al mismo objeto. Si Point es una estructura, la salida es 10 porque la asignación de a to b crea una copia del valor, y esta copia no se ve afectada por la asignación subsiguiente a ax

El ejemplo anterior destaca dos de las limitaciones de las estructuras. Primero, copiar una estructura completa suele ser menos eficiente que copiar una referencia de objeto, por lo que la asignación de parámetros de asignación y valor puede ser más costosa con estructuras que con tipos de referencia. En segundo lugar, a excepción de los parámetros de referencia y salida, no es posible crear referencias a estructuras, lo que descarta su uso en una serie de situaciones.


La fuente a la que hace referencia el OP tiene cierta credibilidad ... pero ¿qué pasa con Microsoft? ¿Cuál es la postura sobre el uso de estructuras? Busqué un aprendizaje adicional de Microsoft , y esto es lo que encontré:

Considere la posibilidad de definir una estructura en lugar de una clase si las instancias del tipo son pequeñas y comúnmente duran poco o están comúnmente incrustadas en otros objetos.

No defina una estructura a menos que el tipo tenga todas las características siguientes:

  1. Lógicamente representa un solo valor, similar a los tipos primitivos (entero, doble, etc.).
  2. Tiene un tamaño de instancia inferior a 16 bytes.
  3. Es inmutable.
  4. No tendrá que ser boxeado con frecuencia.

Microsoft viola sistemáticamente esas reglas

Está bien, # 2 y # 3 de todos modos. Nuestro amado diccionario tiene 2 estructuras internas:

[StructLayout(LayoutKind.Sequential)]  // default for structs
private struct Entry  //<Tkey, TValue>
{
    //  View code at *Reference Source
}

[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator : 
    IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable, 
    IDictionaryEnumerator, IEnumerator
{
    //  View code at *Reference Source
}

* Fuente de referencia

La fuente 'JonnyCantCode.com' obtuvo 3 de 4, bastante perdonable ya que el # 4 probablemente no sea un problema. Si te encuentras encuadrado en una estructura, reconsidera tu arquitectura.

Veamos por qué Microsoft usaría estas estructuras:

  1. Cada estructura, Entry y Enumerator , representan valores únicos.
  2. Velocidad
  3. Entry nunca se pasa como un parámetro fuera de la clase Diccionario. Una investigación adicional muestra que para satisfacer la implementación de IEnumerable, el Diccionario usa la estructura del Enumerator que copia cada vez que se solicita un enumerador ... tiene sentido.
  4. Interno a la clase de diccionario. Enumerator es público porque el Diccionario es enumerable y debe tener la misma accesibilidad a la implementación de la interfaz de IEnumerator, por ejemplo, el captador de IEnumerator.

Actualización : además, tenga en cuenta que cuando una estructura implementa una interfaz, como lo hace Enumerator, y se convierte en ese tipo implementado, la estructura se convierte en un tipo de referencia y se traslada al montón. Interno a la clase de diccionario, Enumerator sigue siendo un tipo de valor. Sin embargo, tan pronto como un método llama a GetEnumerator() , se devuelve un IEnumerator tipo de IEnumerator .

Lo que no vemos aquí es cualquier intento o prueba de requisito para mantener las estructuras inmutables o mantener un tamaño de instancia de solo 16 bytes o menos:

  1. Nada en las estructuras anteriores se declara de readonly , no es inmutable
  2. El tamaño de estas estructuras podría ser más de 16 bytes.
  3. Entry tiene un tiempo de vida indeterminado (desde Add() a Remove() , Clear() o recolección de basura);

Y ... 4. Ambas estructuras almacenan TKey y TValue, que todos sabemos que son bastante capaces de ser tipos de referencia (información de bonificación adicional)

A pesar de las claves de hash, los diccionarios son rápidos en parte porque crear una estructura es más rápido que un tipo de referencia. Aquí, tengo un Dictionary<int, int> que almacena 300,000 enteros aleatorios con claves incrementadas secuencialmente.

Capacidad: 312874
MemSize: 2660827 bytes
Completado Tamaño: 5ms
Tiempo total de llenado: 889ms.

Capacidad : número de elementos disponibles antes de cambiar el tamaño de la matriz interna.

MemSize : determinado al serializar el diccionario en un MemoryStream y obtener una longitud de byte (lo suficientemente precisa para nuestros propósitos).

Cambio de tamaño completado : el tiempo que lleva cambiar el tamaño de la matriz interna de 150862 elementos a 312874 elementos. Cuando Array.CopyTo() que cada elemento se copia de forma secuencial a través de Array.CopyTo() , no está tan mal.

Tiempo total para completar : es cierto que está sesgado debido al registro y un evento OnResize que agregué a la fuente; sin embargo, todavía es impresionante llenar 300k enteros mientras se redimensiona 15 veces durante la operación. Solo por curiosidad, ¿cuál sería el tiempo total para llenar si ya supiera la capacidad? 13ms

Entonces, ¿qué Entry si Entry fuera una clase? ¿Serían estos tiempos o métricas realmente tan diferentes?

Capacidad: 312874
MemSize: 2660827 bytes
Completado Tamaño: 26ms
Tiempo total de llenado: 964ms.

Obviamente, la gran diferencia está en el cambio de tamaño. ¿Alguna diferencia si el Diccionario se inicializa con la Capacidad? No es suficiente para preocuparse por ... 12ms .

Lo que sucede es que, como Entry es una estructura, no requiere inicialización como un tipo de referencia. Esto es tanto la belleza como la perdición del tipo de valor. Para utilizar la Entry como tipo de referencia, tuve que insertar el siguiente código:

/*
 *  Added to satisfy initialization of entry elements --
 *  this is where the extra time is spent resizing the Entry array
 * **/
for (int i = 0 ; i < prime ; i++)
{
    destinationArray[i] = new Entry( );
}
/*  *********************************************** */  

La razón por la que tuve que inicializar cada elemento de la matriz de Entry como un tipo de referencia se puede encontrar en MSDN: Diseño de estructura . En breve:

No proporcione un constructor predeterminado para una estructura.

Si una estructura define un constructor predeterminado, cuando se crean matrices de la estructura, el tiempo de ejecución del lenguaje común ejecuta automáticamente el constructor predeterminado en cada elemento de la matriz.

Algunos compiladores, como el compilador de C #, no permiten que las estructuras tengan constructores predeterminados.

En realidad es bastante simple y tomaremos prestado de las Tres leyes de la robótica de Asimov :

  1. La estructura debe ser segura de usar
  2. La estructura debe realizar su función de manera eficiente, a menos que esto infrinja la regla # 1
  3. La estructura debe permanecer intacta durante su uso, a menos que se requiera su destrucción para cumplir con la regla # 1

... ¿Qué es lo que quitamos de esto ? En resumen, ser responsable con el uso de tipos de valor. Son rápidos y eficientes, pero tienen la capacidad de causar muchos comportamientos inesperados si no se mantienen adecuadamente (es decir, copias no intencionales).


Las estructuras son buenas para la representación atómica de datos, donde dichos datos se pueden copiar varias veces mediante el código. En general, la clonación de un objeto es más costosa que copiar una estructura, ya que implica asignar la memoria, ejecutar el constructor y desasignar / recolectar basura cuando se hace con él.


Primero: escenarios de interoperabilidad o cuando necesita especificar el diseño de la memoria

Segundo: cuando los datos son casi del mismo tamaño que un puntero de referencia de todos modos.


Siempre que no necesite polimorfismo, desee semántica de valor y desee evitar la asignación de almacenamiento dinámico y la sobrecarga de recolección de basura asociada. La advertencia, sin embargo, es que las estructuras (arbitrariamente grandes) son más costosas de pasar que las referencias de clase (generalmente una palabra de máquina), por lo que las clases podrían terminar siendo más rápidas en la práctica.


Utilizo estructuras para empaquetar o desempacar cualquier tipo de formato de comunicación binario. Eso incluye leer o escribir en el disco, listas de vértices de DirectX, protocolos de red o tratar con datos cifrados / comprimidos.

Las tres pautas que enumeras no me han sido útiles en este contexto. Cuando tenga que escribir cuatrocientos bytes de material en un Orden en particular, definiré una estructura de cuatrocientos bytes, y la llenaré con los valores no relacionados que se supone que tiene, y me voy configurarlo de cualquier manera tiene el mayor sentido en el momento. (De acuerdo, cuatrocientos bytes serían bastante extraños, pero cuando estaba escribiendo archivos Excel para ganarme la vida, manejaba estructuras de hasta unos cuarenta bytes en todo, porque ese es el tamaño de algunos de los registros BIFF).


Brevemente, use struct si:

1- Las propiedades / campos de sus objetos no necesitan ser cambiados. Quiero decir que solo quieres darles un valor inicial y luego leerlos.

2- Las propiedades y los campos en su objeto son tipo de valor y no son tan grandes.

Si ese es el caso, puede aprovechar las estructuras para un mejor rendimiento y una asignación de memoria optimizada, ya que utilizan solo pilas en lugar de pilas y pilas (en clases)


Los tipos de estructura o valor se pueden usar en los siguientes escenarios:

  1. Si desea evitar que el objeto sea recogido por la recolección de basura.
  2. Si es un tipo simple y ninguna función miembro modifica sus campos de instancia
  3. Si no es necesario derivar de otros tipos o derivarse a otros tipos.

Puede saber más sobre los tipos de valor y los tipos de valores aquí en este enlace


Me parece que la estructura no tiene una semántica fuerte que le dé al usuario una idea sólida de cuándo usarla.

Se asemeja a una clase, pero la mayoría de las características del lago. Es una especie de versión degradada de una clase. Hay muchos comentarios sobre cuándo no usarlo, pero muy pocos sobre cuándo usarlo.

En mi opinión, no hay ninguna razón por la que se deba implementar struct en un lenguaje OO en primer lugar. En realidad, el tipo primitivo no debería existir en un lenguaje OO puro, pero estoy divagando.

Podría ser una forma de optimizar cosas. Un tipo de cosas sin boxeo que se pueden optimizar en algún sitio de llamadas.

Mi 2 centavo, diría que violó el principio KISS del lenguaje y lo evito tanto como puedo.


Hice un pequeño punto de referencia con BenchmarkDotNet para comprender mejor el beneficio de "estructura" en números. Estoy probando el bucle a través de una matriz (o lista) de estructuras (o clases). La creación de esas matrices o listas está fuera del alcance del índice de referencia: está claro que la "clase" es más pesada, utilizará más memoria e involucrará a GC.

Así que la conclusión es: tenga cuidado con LINQ y las estructuras ocultas boxing / unboxing y el uso de structs para microoptimizaciones se queda estrictamente con los arreglos.

PS Otro punto de referencia acerca de pasar struct / class a través de la pila de llamadas está disponible en https://.com/a/47864451/506147

BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233542 Hz, Resolution=309.2584 ns, Timer=TSC
  [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Core   : .NET Core 4.6.25211.01, 64bit RyuJIT


          Method |  Job | Runtime |      Mean |     Error |    StdDev |       Min |       Max |    Median | Rank |  Gen 0 | Allocated |
---------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|----------:|-----:|-------:|----------:|
   TestListClass |  Clr |     Clr |  5.599 us | 0.0408 us | 0.0382 us |  5.561 us |  5.689 us |  5.583 us |    3 |      - |       0 B |
  TestArrayClass |  Clr |     Clr |  2.024 us | 0.0102 us | 0.0096 us |  2.011 us |  2.043 us |  2.022 us |    2 |      - |       0 B |
  TestListStruct |  Clr |     Clr |  8.427 us | 0.1983 us | 0.2204 us |  8.101 us |  9.007 us |  8.374 us |    5 |      - |       0 B |
 TestArrayStruct |  Clr |     Clr |  1.539 us | 0.0295 us | 0.0276 us |  1.502 us |  1.577 us |  1.537 us |    1 |      - |       0 B |
   TestLinqClass |  Clr |     Clr | 13.117 us | 0.1007 us | 0.0892 us | 13.007 us | 13.301 us | 13.089 us |    7 | 0.0153 |      80 B |
  TestLinqStruct |  Clr |     Clr | 28.676 us | 0.1837 us | 0.1534 us | 28.441 us | 28.957 us | 28.660 us |    9 |      - |      96 B |
   TestListClass | Core |    Core |  5.747 us | 0.1147 us | 0.1275 us |  5.567 us |  5.945 us |  5.756 us |    4 |      - |       0 B |
  TestArrayClass | Core |    Core |  2.023 us | 0.0299 us | 0.0279 us |  1.990 us |  2.069 us |  2.013 us |    2 |      - |       0 B |
  TestListStruct | Core |    Core |  8.753 us | 0.1659 us | 0.1910 us |  8.498 us |  9.110 us |  8.670 us |    6 |      - |       0 B |
 TestArrayStruct | Core |    Core |  1.552 us | 0.0307 us | 0.0377 us |  1.496 us |  1.618 us |  1.552 us |    1 |      - |       0 B |
   TestLinqClass | Core |    Core | 14.286 us | 0.2430 us | 0.2273 us | 13.956 us | 14.678 us | 14.313 us |    8 | 0.0153 |      72 B |
  TestLinqStruct | Core |    Core | 30.121 us | 0.5941 us | 0.5835 us | 28.928 us | 30.909 us | 30.153 us |   10 |      - |      88 B |

Código:

[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkRef
    {
        public class C1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        public struct S1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        List<C1> testListClass = new List<C1>();
        List<S1> testListStruct = new List<S1>();
        C1[] testArrayClass;
        S1[] testArrayStruct;
        public BenchmarkRef()
        {
            for(int i=0;i<1000;i++)
            {
                testListClass.Add(new C1  { Text1= i.ToString(), Text2=null, Text3= i.ToString() });
                testListStruct.Add(new S1 { Text1 = i.ToString(), Text2 = null, Text3 = i.ToString() });
            }
            testArrayClass = testListClass.ToArray();
            testArrayStruct = testListStruct.ToArray();
        }

        [Benchmark]
        public int TestListClass()
        {
            var x = 0;
            foreach(var i in testListClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayClass()
        {
            var x = 0;
            foreach (var i in testArrayClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestListStruct()
        {
            var x = 0;
            foreach (var i in testListStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayStruct()
        {
            var x = 0;
            foreach (var i in testArrayStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestLinqClass()
        {
            var x = testListClass.Select(i=> i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }

        [Benchmark]
        public int TestLinqStruct()
        {
            var x = testListStruct.Select(i => i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }
    }

La estructura C # es una alternativa liviana a una clase. Puede hacer casi lo mismo que una clase, pero es menos "caro" usar una estructura en lugar de una clase. La razón de esto es un poco técnica, pero para resumir, las nuevas instancias de una clase se colocan en el montón, donde las estructuras recientemente instanciadas se colocan en la pila. Además, no está tratando con referencias a estructuras, como con clases, sino que está trabajando directamente con la instancia de estructura. Esto también significa que cuando se pasa una estructura a una función, es por valor, en lugar de como una referencia. Hay más sobre esto en el capítulo sobre parámetros de funciones.

Por lo tanto, debe usar estructuras cuando desee representar estructuras de datos más simples, y especialmente si sabe que va a crear una instancia de muchas de ellas. Hay muchos ejemplos en el marco .NET, donde Microsoft ha utilizado estructuras en lugar de clases, por ejemplo, la estructura de Punto, Rectángulo y Color.


Los tipos de estructura en C # u otros lenguajes .net se usan generalmente para contener cosas que deberían comportarse como grupos de valores de tamaño fijo. Un aspecto útil de los tipos de estructura es que los campos de una instancia de tipo de estructura pueden modificarse modificando la ubicación de almacenamiento en la que se encuentra, y de ninguna otra manera. Es posible codificar una estructura de tal manera que la única forma de mutar cualquier campo es construir una instancia completamente nueva y luego usar una asignación de estructura para mutar todos los campos del objetivo sobrescribiéndolos con valores de la nueva instancia, pero a menos que una estructura no proporcione ningún medio para crear una instancia en la que sus campos tengan valores no predeterminados, todos sus campos serán mutables si y si la estructura misma se almacena en una ubicación mutable.

Tenga en cuenta que es posible diseñar un tipo de estructura de modo que esencialmente se comporte como un tipo de clase, si la estructura contiene un campo de tipo de clase privado, y redirige sus propios miembros a los del objeto de clase envuelto. Por ejemplo, a PersonCollectionpodría ofrecer propiedades SortedByNamey SortedById, ambas de ellas contienen una referencia "inmutable" a PersonCollection(establecida en su constructor) e implementan GetEnumeratorllamando a creator.GetNameSortedEnumeratoro creator.GetIdSortedEnumerator. Dichas estructuras se comportarían de manera muy similar a una referencia a PersonCollection, excepto que sus GetEnumeratormétodos estarían vinculados a métodos diferentes en el PersonCollection. También se podría tener una estructura que envuelva una parte de una matriz (por ejemplo, se podría definir una ArrayRange<T>estructura que contendría una T[]llamada Arr, una int Offsety una int).Length, con una propiedad indexada que, para un índice idxen el rango de 0 a Length-1, tendría acceso Arr[idx+Offset]). Desafortunadamente, si fooes una instancia de solo lectura de tal estructura, las versiones actuales del compilador no permitirán operaciones como foo[3]+=4;porque no tienen manera de determinar si tales operaciones intentarán escribir en los campos de foo.

También es posible diseñar una estructura que se comporte como un tipo de valor que contenga una colección de tamaño variable (que aparecerá como copia siempre que sea la estructura), pero la única manera de hacer ese trabajo es asegurarse de que no haya ningún objeto al que la estructura La estructura contiene una referencia que estará expuesta a cualquier cosa que pueda mutarla. Por ejemplo, uno podría tener una estructura similar a una matriz que contiene una matriz privada, y cuyo método de "poner" indexado crea una nueva matriz cuyo contenido es similar al original, excepto por un elemento modificado. Desafortunadamente, puede ser algo difícil hacer que tales estructuras funcionen eficientemente. Si bien hay momentos en que la semántica de la estructura puede ser conveniente (por ejemplo, poder pasar una colección similar a una matriz a una rutina, tanto la persona que llama como la persona que llama saben que el código externo no modificará la colección).puede ser mejor que requerir que tanto el llamante como el que recibe la llamada para copiar de manera defensiva cualquier información que se les proporcione), el requisito de que las referencias de clase apunten a objetos que nunca serán mutados es a menudo una restricción muy severa.


No, no estoy totalmente de acuerdo con las reglas. Son buenas pautas a considerar con el rendimiento y la estandarización, pero no a la luz de las posibilidades.

Como puede ver en las respuestas, hay un registro de formas creativas para usarlas. Por lo tanto, estas pautas deben ser simplemente eso, siempre por el bien del rendimiento y la eficiencia.

En este caso, uso clases para representar objetos del mundo real en su forma más grande, uso estructuras para representar objetos más pequeños que tienen usos más exactos. La forma en que lo dijiste, "un todo más cohesivo". La palabra clave es cohesivo. Las clases serán elementos más orientados a objetos, mientras que las estructuras pueden tener algunas de esas características, en una escala más pequeña. OMI.

Los uso mucho colocando etiquetas Treeview y Listview a las que se puede acceder muy rápidamente a los atributos estáticos comunes. Me costaría conseguir esta información de otra manera. Por ejemplo, en mis aplicaciones de base de datos, uso una vista de árbol donde tengo tablas, SP, funciones o cualquier otro objeto. Creo y relleno mi estructura, la pongo en la etiqueta, la extraigo, obtengo los datos de la selección, etc. Yo no haría esto con una clase!

Intento mantenerlos pequeños, los uso en situaciones de una sola instancia y evito que cambien. Es prudente tener en cuenta la memoria, la asignación y el rendimiento. Y las pruebas son tan necesarias.


Solo estaba tratando con Windows Communication Foundation [WCF] Named Pipe y noté que tiene sentido usar Structs para asegurar que el intercambio de datos sea de tipo valor en lugar de tipo de referencia .


Struct se puede utilizar para mejorar el rendimiento de la recolección de basura. Si bien normalmente no tiene que preocuparse por el rendimiento del GC, hay escenarios en los que puede ser un asesino. Como grandes cachés en aplicaciones de baja latencia. Vea esta publicación para un ejemplo:

http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/


Una clase es un tipo de referencia. Cuando se crea un objeto de la clase, la variable a la que se asigna el objeto contiene solo una referencia a esa memoria. Cuando la referencia del objeto se asigna a una nueva variable, la nueva variable se refiere al objeto original. Los cambios realizados a través de una variable se reflejan en la otra variable porque ambos se refieren a los mismos datos. Una estructura es un tipo de valor. Cuando se crea una estructura, la variable a la que se asigna la estructura contiene los datos reales de la estructura. Cuando la estructura se asigna a una nueva variable, se copia. La nueva variable y la variable original, por lo tanto, contienen dos copias separadas de los mismos datos. Los cambios realizados en una copia no afectan a la otra copia. En general, las clases se utilizan para modelar comportamientos más complejos, o los datos que se pretenden modificar después de crear un objeto de clase.Las estructuras son más adecuadas para estructuras de datos pequeñas que contienen principalmente datos que no se pretende modificar después de que se crea la estructura.

Clases y Estructuras (Guía de Programación C #)





struct