valores valor traduccion tener significado que objeto debe acepta c# clr nullable

c# - valor - ¿Por qué Nullable<T> es una estructura?



nullable sql (4)

Me preguntaba por qué Nullable<T> es un tipo de valor, si está diseñado para imitar el comportamiento de los tipos de referencia. Entiendo cosas como la presión GC, pero no me siento convencido: si queremos que int actúe como referencia, probablemente estemos de acuerdo con todas las consecuencias de tener un tipo de referencia real. No veo ninguna razón por la que Nullable<T> no sea solo una versión en caja de T struct.

Como tipo de valor:

  1. todavía necesita ser encuadrado y sin encajonar, y más, el boxeo debe ser un poco diferente que con las estructuras "normales" (para tratar el nulo de valor nulo como null real)
  2. debe tratarse de manera diferente al verificar el valor nulo (hecho simplemente en Equals , no es un problema real)
  3. es mutable, rompiendo la regla de que las estructuras deben ser inmutables (ok, es lógicamente inmutable)
  4. debe tener una restricción especial para no permitir la recursión como Nullable<Nullable<T>>

¿El hecho de que Nullable<T> un tipo de referencia no resuelva esos problemas?

reformulado y actualizado:

He modificado un poco mi lista de motivos, pero mi pregunta general sigue abierta:

¿Cómo será la clase de referencia Nullable<T> peor que la implementación del tipo de valor actual? ¿Es solo la presión del GC y la regla "pequeña, inmutable"? Todavía me resulta extraño ...


Codifiqué MyNullable como una clase. Realmente no puedo entender por qué no puede ser una clase, además de evitar la presión de la memoria del montón.

namespace ClassLibrary1

{usando NFluent;

using NUnit.Framework; [TestFixture] class MyNullableShould { [Test] public void operator_equals_btw_nullable_and_value_works() { var myNullable = new MyNullable<int>(1); Check.That(myNullable == 1).IsEqualTo(true); Check.That(myNullable == 2).IsEqualTo(false); } [Test] public void Can_be_comparedi_with_operator_equal_equals() { var myNullable = new MyNullable<int>(1); var myNullable2 = new MyNullable<int>(1); Check.That(myNullable == myNullable2).IsTrue(); Check.That(myNullable == myNullable2).IsTrue(); var myNullable3 = new MyNullable<int>(2); Check.That(myNullable == myNullable3).IsFalse(); } }

} namespace ClassLibrary1 {using System;

public class MyNullable<T> where T : struct { internal T value; public MyNullable(T value) { this.value = value; this.HasValue = true; } public bool HasValue { get; } public T Value { get { if (!this.HasValue) throw new Exception("Cannot grab value when has no value"); return this.value; } } public static explicit operator T(MyNullable<T> value) { return value.Value; } public static implicit operator MyNullable<T>(T value) { return new MyNullable<T>(value); } public static bool operator ==(MyNullable<T> n1, MyNullable<T> n2) { if (!n1.HasValue) return !n2.HasValue; if (!n2.HasValue) return false; return Equals(n1.value, n2.value); } public static bool operator !=(MyNullable<T> n1, MyNullable<T> n2) { return !(n1 == n2); } public override bool Equals(object other) { if (!this.HasValue) return other == null; if (other == null) return false; return this.value.Equals(other); } public override int GetHashCode() { return this.HasValue ? this.value.GetHashCode() : 0; } public T GetValueOrDefault() { return this.value; } public T GetValueOrDefault(T defaultValue) { return this.HasValue ? this.value : defaultValue; } public override string ToString() { return this.HasValue ? this.value.ToString() : string.Empty; } }

}


La razón es que no fue diseñado para actuar como un tipo de referencia. Fue diseñado para actuar como un tipo de valor, excepto en uno solo. Veamos algunas formas en que los tipos de valor y los tipos de referencia difieren.

La principal diferencia entre un valor y un tipo de referencia es que el tipo de valor es autónomo (la variable que contiene el valor real), mientras que un tipo de referencia se refiere a otro valor.

Algunas otras diferencias están implicadas por esto. El hecho de que podemos aliar tipos de referencia directamente (lo que tiene efectos buenos y malos) proviene de esto. Así también las diferencias en lo que significa igualdad:

Un tipo de valor tiene un concepto de igualdad basado en el valor contenido, que opcionalmente se puede redefinir (existen restricciones lógicas sobre cómo puede ocurrir esta redefinición *). Un tipo de referencia tiene un concepto de identidad que carece de significado con los tipos de valor (ya que no pueden tener un alias directamente, por lo que dos de esos valores no pueden ser idénticos) que no pueden redefinirse, lo que también da el valor predeterminado a su concepto de igualdad. De forma predeterminada, == trata con esta igualdad basada en valores cuando se trata de tipos de valores †, pero con identidad cuando se trata de tipos de referencia. Además, incluso cuando a un tipo de referencia se le otorga un concepto de igualdad basado en valores, y se usa para == , nunca pierde la capacidad de ser comparado con otra referencia de identidad.

Otra diferencia que conlleva esto es que los tipos de referencia pueden ser nulos: un valor que se refiere a otro valor permite un valor que no se refiere a ningún valor, que es lo que es una referencia nula.

Además, algunas de las ventajas de mantener pequeños tipos de valor se relacionan con esto, ya que, al estar basados ​​en el valor, se copian por valor cuando se pasan a las funciones.

Algunas otras diferencias están implícitas pero no están implicadas por esto. Que a menudo es una buena idea hacer que los tipos de valor sean inmutables, está implícito, pero no se debe a la diferencia central, ya que si bien hay ventajas que se pueden encontrar sin tener en cuenta los aspectos de la implementación, también hay ventajas al hacerlo con tipos de referencia (de hecho, algunos se relacionan con la seguridad con los alias se aplican de forma más inmediata a los tipos de referencia) y las razones por las que se puede romper esta directriz, por lo que no es una regla difícil y rápida (con los tipos de valor anidados, los riesgos implicados se reducen tanto que tendría pocos remordimientos para hacer que un tipo de valor anidado sea mutable , aunque mi estilo se inclina fuertemente para hacer que incluso los tipos de referencia sean inmutables cuando sea práctico).

Algunas diferencias adicionales entre los tipos de valor y los tipos de referencia son, sin duda, detalles de la implementación. Que un tipo de valor en una variable local tenga el valor almacenado en la pila se ha argumentado como un detalle de implementación; Probablemente sea bastante obvio si su implementación tiene una pila, y ciertamente una importante en algunos casos, pero no es esencial para la definición. A menudo, también se sobrestima (para empezar, un tipo de referencia en una variable local también tiene la referencia en sí misma en la pila, en otro, hay muchas ocasiones en que un valor de tipo de valor se almacena en el montón).

Algunas ventajas adicionales en los tipos de valor que son pequeños se relacionan con esto.

Ahora, Nullable<T> es un tipo que se comporta como un tipo de valor en todas las formas descritas anteriormente, excepto que puede tomar un valor nulo. Tal vez la cuestión de los valores locales que se almacenan en la pila no es tan importante (es más un detalle de implementación que cualquier otra cosa), pero el resto es inherente a cómo se define.

Nullable<T> se define como

struct Nullable<T> { private bool hasValue; internal T value; /* methods and properties I won''t go into here */ }

La mayor parte de la implementación desde este punto es obvia. Se necesita algo de manejo especial para permitir que se le asigne un default(Nullable<T>) tratado como si se hubiera asignado el default(Nullable<T>) , y un manejo especial cuando está en el recuadro, y luego sigue el resto (incluido el hecho de que se puede comparar para la igualdad con el valor nulo ).

Si Nullable<T> era un tipo de referencia, tendríamos que tener un manejo especial para permitir que todo el resto ocurra, junto con un manejo especial para las características de cómo .NET ayuda al desarrollador (como necesitaríamos un manejo especial para hacerla descender de ValueType ). Ni siquiera estoy seguro de si sería posible.

* Hay algunas restricciones sobre cómo podemos redefinir la igualdad. Combinando esas reglas con las que se utilizan en los valores predeterminados, generalmente podemos permitir que dos valores se consideren iguales que se considerarían desiguales por defecto, pero rara vez tiene sentido considerar dos valores desiguales que el valor predeterminado consideraría iguales. Una excepción es el caso donde una estructura contiene solo tipos de valor, pero donde dichos tipos de valor redefinen la igualdad. Este es el resultado de una optimización, y generalmente se considera un error más que por diseño.

† Una excepción son los tipos de punto flotante. Debido a la definición de tipos de valor en el estándar CLI, double.NaN.Equals(double.NaN) y float.NaN.Equals(float.NaN) devuelven true . Pero debido a la definición de NaN en ISO 60559, float.NaN == float.NaN y double.NaN == double.NaN devuelven false.


No es mutable; revisar otra vez.

El boxeo es diferente también; una "caja" vacía a nula.

Pero; es pequeño (apenas más grande que T), inmutable y encapsula solo estructuras, ideal como estructura. Quizás más importante, siempre que T sea un verdadero "valor", entonces también lo es T? un "valor" lógico.


Editado para abordar la pregunta actualizada ...

Puede encuadrar y desempaquetar objetos si desea usar una estructura como referencia.

Sin embargo, el tipo Nullable<> básicamente permite mejorar cualquier tipo de valor con un indicador de estado adicional que indica si el valor se utilizará como null o si el stuct es "válido".

Para responder a sus preguntas:

  1. Esto es una ventaja cuando se usa en colecciones, o debido a las diferentes semánticas (copia en lugar de referencia)

  2. No, no lo hace. El CLR respeta esto al hacer boxeo y desempaquetar, de modo que en realidad nunca Nullable<> una instancia de Nullable<> . Encajonar un Nullable<> que "no tiene" ningún valor devolverá una referencia null , y unboxing hace lo contrario.

  3. No

  4. Una vez más, este no es el caso. De hecho, las restricciones genéricas para una estructura no permiten el uso de estructuras anulables. Esto tiene sentido debido al comportamiento especial de boxeo / desempaquetado. Por lo tanto, si tiene una where T: struct para restringir un tipo genérico, se anularán los tipos anulables. Dado que esta restricción también se define en el Nullable<T> , no puede Nullable<T> , sin ningún tratamiento especial para evitarlo.

¿Por qué no usar referencias? Ya mencioné las importantes diferencias semánticas. Pero aparte de esto, los tipos de referencia utilizan mucho más espacio de memoria: cada referencia, especialmente en entornos de 64 bits, utiliza no solo la memoria del montón para la instancia, sino también la memoria para la referencia, la información del tipo de instancia, los bits de bloqueo, etc. Entonces, aparte de la semántica y las diferencias de rendimiento (direccionamiento indirecto a través de referencia), termina usando un múltiplo de la memoria utilizada para la entidad en sí para la mayoría de las entidades comunes. Y el GC obtiene más objetos para manejar, lo que hará que el rendimiento total en comparación con las estructuras sea aún peor.