La distinción importante entre ValueTypes y tipos de referencia es que los tipos de valores tienen estos "valores semánticos". Un DateTime, Int32 y todos los otros tipos de valores no tienen identidad, un Int32 "42" es esencialmente indistinguible de cualquier otro Int32 con el mismo valor.
Todos los tipos de valor "objetos" existen ya sea en la pila o como parte de un objeto de tipo de referencia. Un caso especial es cuando lanzas una instancia de tipo de valor a un objeto o una interfaz; esto se llama "boxeo", y simplemente crea un objeto tipo referencia ficticio que solo contiene el valor que se puede extraer de nuevo ("unboxed") .
Los tipos de referencia, por otro lado, tienen una identidad. un "nuevo objeto()" no equivale a ningún otro "nuevo objeto()", porque son instancias separadas en el montón de GC. Algunos tipos de referencia proporcionan el método Equals y los operadores sobrecargados para que se comporten de forma similar a los valores, por ejemplo. una cadena "abc" es igual a otra cadena "abc" incluso si en realidad son dos objetos diferentes.
Por lo tanto, cuando tiene una referencia, puede contener la dirección de un objeto válido o puede ser nula. Cuando los objetos de tipo de valor son todos cero, son simplemente cero. P.ej. un entero cero, un flotante cero, booleano falso o DateTime.MinValue. Si necesita distinguir entre "cero" y "valor perdido/nulo", necesita usar un indicador booleano separado o, mejor aún, usar la clase T> Nullable < en .NET 2.0. Que es simplemente el valor más un indicador booleano. También hay soporte en el CLR para que el boxeo de un Nullable con HasValue = falso resulte en una referencia nula, no en una estructura enmarcada con falso + cero, como lo haría si implementara esta estructura usted mismo.
Es Jon no John. –