C#:estructuras frente a clases (referencia frente a tipo de valor) y cuándo usar qué

C#:estructuras frente a clases (referencia frente a tipo de valor) y cuándo usar qué

Nunca uso estructuras, ahí lo he dicho. No porque no me gusten o sienta que no tienen ningún uso, pero rara vez he visto que otros desarrolladores los usen. También pasé años en mi carrera antes de ver que se usaban en una base de código. Aquí estoy hablando de las estructuras definidas por los desarrolladores, no las integradas en el lenguaje (DateTime, int, etc.)

Entonces, para arrojar algo de luz sobre si lo que estoy haciendo está bien o mal, quería echar un nuevo vistazo a las estructuras, lo que motivó este artículo.

Tipos de valor y tipos de referencia

Hay una diferencia fundamental entre los dos, las estructuras son tipos de valor y las clases son tipos de referencia. Pero, ¿qué significa esto?

Para empezar, hay una gran diferencia cuando haces asignaciones en tu código. Las asignaciones de tipo de referencia copian la referencia donde las asignaciones de valor copian el valor. Lo que significa que cuanto más hay para copiar, mayores son las operaciones para asignar a un tipo de valor. Por lo tanto, los tipos de referencia son más baratos de asignar cuando se trabaja con estructuras grandes, ya que solo tienen que mover un puntero.

Las estructuras y las clases también se asignan de manera diferente en cuanto a la memoria, los tipos de valor van en la pila y los tipos de referencia en el montón (con un puntero hacia él). Si está interesado en la asignación de memoria en C#, le sugiero este artículo. Para este tema, clases frente a estructura, la parte importante es que:las asignaciones y desasignaciones de tipos de valor normalmente son más rápidas que las asignaciones y desasignaciones de tipos de referencia

La mayor diferencia de los dos (en mi opinión) es que los tipos de valor se transmiten por copia y los tipos de referencia por referencia. Esto puede conducir a algunos resultados no deseados, si no sabe cómo funcionan las estructuras. A continuación he hecho un pequeño ejemplo:

static void Main(string[] args)
{
    Struct1 struct1 = new Struct1();
    struct1.I = 1;
    SetITo2(struct1);
    Console.WriteLine(struct1.I); //still 1
    Console.ReadKey();
}

public static void SetITo2(Struct1 struct1)
{
    struct1.I = 2;
}

public struct Struct1
{
    public int I { get; set; }
}

En lo anterior declaro la variable struct1 con una propiedad I que es un número entero. Luego asigno el valor 1 a I . El tipo struct1 es un tipo de valor. Luego llamo a un método que asigna 2 a esta variable. Sin embargo, esto no cambia el valor de i variable en el método Main. Esto se debe a que se pasa al SetTo2() método como una copia y no como referencia. Podemos evitar esto pasándolo como referencia (usando el ref palabra clave):

static void Main(string[] args)
{
    Struct1 struct1 = new Struct1();
    struct1.I = 1;
    SetITo2(ref struct1);
    Console.WriteLine(struct1.I); //now 2
    Console.ReadKey();
}

public static void SetITo2(ref Struct1 struct1)
{
    struct1.I = 2;
}

Otra forma de lograr esto sería envolver nuestra variable i en un class (tipo de referencia) en lugar de un struct :

static void Main(string[] args)
{
    Class1 class1 = new Class1();
    class1.I = 1;
    SetITo2(class1);
    Console.WriteLine(class1.I); //now 2
    Console.ReadKey();
}

public static void SetITo2(Class1 class1)
{
    class1.I = 2;
}

public class Class1
{
    public int I { get; set; }
}

No saber cómo se transmiten los tipos (por referencia o por copia) puede generar un comportamiento extraño. Creo que esta es la diferencia más importante que hay que saber sobre los tipos de valor y referencia. En mis ejemplos

Otra nota es que, idealmente, las estructuras también deberían ser inmutables. El primero de los ejemplos anteriores también podría haberse evitado si la estructura hubiera sido inmutable (si solo hubiera podido establecer el valor una vez).

Las estructuras tienen varias limitaciones que las clases no tienen. Las estructuras no pueden:

  • Derivar de otras estructuras o clases
  • Defina explícitamente un constructor sin parámetros predeterminado

También puede convertir un tipo de valor en un tipo de valor y viceversa. Esto se llama boxing y unboxing. Un ejemplo de esto sería:

int i = 0;
Object k = i;

En lo anterior, nuestro tipo de valor i está encuadrado en el tipo de referencia k. Lo que significa que ahora es un tipo de referencia y no un tipo de valor. He escrito un artículo más completo sobre cómo encajonar y desempaquetar aquí.

En resumen

Por lo general, desea utilizar clases. Pero hay un par de excepciones a esto:

  • Es inmutable
  • Es de tamaño pequeño (<16 bytes)
  • No tendrá que ser empaquetado y desempaquetado con frecuencia.

¡Eso es! Espero que hayas disfrutado mi publicación sobre estructuras vs clases. ¡Déjame saber lo que piensas en los comentarios!

Recursos

Para este artículo he utilizado los siguientes recursos:

  • Elegir entre clase y estructura
  • ¿Cuál es la diferencia entre estructura y clase en .NET?
  • ¿Cuándo usas una estructura en lugar de una clase?