Pregunta

Notamos que muchos errores en nuestro software desarrollado en C # (o Java) causan una NullReferenceException.

¿Hay alguna razón por la que " null " ¿Incluso se ha incluido en el idioma?

Después de todo, si no hubiera " null " ;, no tendría ningún error, ¿verdad?

En otras palabras, ¿qué función en el idioma no podría funcionar sin nulo?

¿Fue útil?

Solución

Anders Hejlsberg, " C # father " ;, acaba de hablar sobre ese punto en su entrevista en Computerworld :

  

Por ejemplo, en el sistema de tipos no tenemos separación entre los tipos de valor y referencia y la nulabilidad de los tipos. Esto puede parecer un tanto torpe o un poco técnico, pero en C # los tipos de referencia pueden ser nulos, como cadenas, pero los tipos de valor no pueden ser nulos. Seguro que sería bueno tener tipos de referencia no anulables, por lo que podría declarar que "esta cadena nunca puede ser nula, y quiero que el compilador compruebe que nunca puedo golpear un puntero nulo aquí".

     

El 50% de los errores con los que se encuentra la gente hoy en día, la codificación con C # en nuestra plataforma, y ??lo mismo ocurre con Java, es probable que sean excepciones de referencia nula. Si hubiéramos tenido un sistema de tipo más sólido que le permitiera decir que "este parámetro nunca puede ser nulo, y el compilador, por favor, verifique eso en cada llamada, haciendo un análisis estático del código". Entonces podríamos haber eliminado las clases de errores.

Cyrus Najmabadi, ex ingeniero de diseño de software del equipo de C # (que ahora trabaja en Google) discute sobre ese tema en su blog: ( 1st , 2nd , 3rd , 4th ). Parece que el mayor obstáculo para la adopción de tipos no anulables es que la notación perturbaría los hábitos de los programadores y la base de código. Es probable que algo como el 70% de las referencias de los programas C # terminen como no-nulables.

Si realmente desea tener un tipo de referencia no anulable en C #, debe intentar usar Spec # que es una extensión de C # que permite el uso de "! " como un signo no anulable.

static string AcceptNotNullObject(object! s)
{
    return s.ToString();
}

Otros consejos

La nulidad es una consecuencia natural de los tipos de referencia. Si tiene una referencia, tiene que hacer referencia a algún objeto, o ser nulo. Si prohibiera la nulidad, siempre tendría que asegurarse de que cada variable se inicializó con alguna expresión que no sea nula, e incluso entonces tendría problemas si las variables se leyeran durante la fase de inicialización.

¿Cómo propondría eliminar el concepto de nulidad?

Como muchas cosas en la programación orientada a objetos, todo se remonta a ALGOL. Tony Hoare justo lo llamó su "error de mil millones de dólares". "En cualquier caso, eso es un eufemismo".

Aquí está una tesis realmente interesante sobre cómo hacer La nulabilidad no es la predeterminada en Java. Los paralelos a C # son obvios.

Null en C # es principalmente un remanente de C ++, que tenía punteros que no apuntaban a nada en la memoria (o mejor dicho, dirección 0x00 ). En esto entrevista , Anders Hejlsberg dice que le hubiera gustado haber agregado tipos de referencia no anulables en C #.

Null también tiene un lugar legítimo en un sistema de tipos, sin embargo, como algo similar a la tipo de fondo (donde object es el tipo top ). En lisp, el tipo inferior es NIL y en Scala es Nothing .

Hubiera sido posible diseñar C # sin ningún nulo, pero luego tendría que encontrar una solución aceptable para los usos que la gente suele tener para null , como valor unitarioizado , no encontrado , valor predeterminado , valor indefinido y Ninguno < T > . Probablemente hubiera habido menos adopción entre los programadores de C ++ y Java si hubieran tenido éxito en eso de todos modos. Al menos hasta que vieron que los programas de C # nunca tuvieron ninguna excepción de puntero nulo.

Eliminar nulo no resolvería mucho. Necesitaría tener una referencia predeterminada para la mayoría de las variables que se establece en init. En lugar de excepciones de referencia nula, obtendría un comportamiento inesperado porque la variable está apuntando a los objetos incorrectos. Al menos las referencias nulas fallan rápido en lugar de causar un comportamiento inesperado.

Puedes mirar el patrón de objetos nulos para encontrar una manera de resolver parte de este problema

Null es una característica extremadamente poderosa. ¿Qué haces si tienes una ausencia de un valor? Es nulo!

Una escuela de pensamiento es nunca volver nula, otra es siempre. Por ejemplo, algunos dicen que debes devolver un objeto válido pero vacío.

Prefiero nulo ya que para mí es una indicación más verdadera de lo que realmente es. Si no puedo recuperar una entidad de mi capa de persistencia, quiero nulo. No quiero un valor vacío. Pero ese soy yo

Es especialmente útil con los primitivos. Por ejemplo, si tengo verdadero o falso, pero se usa en un formulario de seguridad, donde un permiso puede ser Permitir, Denegar o no configurarse. Bueno, quiero que eso no sea nulo. ¿Entonces puedo usar bool?

Hay muchas más cosas sobre las que podría hablar, pero las dejaré ahí.

  

Después de todo, si no hubiera " null " ;, no tendría ningún error, ¿no?

La respuesta es NO . El problema no es que C # permita nulo, el problema es que tiene errores que se manifiestan con la excepción NullReferenceException. Como ya se ha dicho, los nulos tienen un propósito en el idioma para indicar un " vacío " tipo de referencia, o un no valor (vacío / nada / desconocido).

Nulo no causa NullPointerExceptions ...

Los programadores causan NullPointerExceptions.

Sin nulos, volvemos a utilizar un valor arbitrario real para determinar que el valor de retorno de una función o método no era válido. Aún tienes que verificar el -1 devuelto (o lo que sea), eliminar los nulos no resolverá mágicamente la pereza, sino que la confuscará.

La pregunta puede interpretarse como " ¿Es mejor tener un valor predeterminado para cada tipo de referencia (como String.Empty) o nulo? " ;. En esta perspectiva, preferiría tener nulos, porque;

  • No me gustaría escribir un valor predeterminado constructor para cada clase que escribo.
  • No me gustaría algo innecesario memoria que se asignará para tal valores por defecto.
  • comprobando si un la referencia es nula es más barata comparaciones de valor.
  • Es altamente posible tener más errores que son Más difícil de detectar, en lugar de NullReferanceExceptions. Es un bueno tener tal excepción lo que indica claramente que soy haciendo (asumiendo) algo mal.

" Null " Se incluye en el lenguaje porque tenemos tipos de valor y tipos de referencia. Probablemente sea un efecto secundario, pero creo que es bueno. Nos da mucho poder sobre la forma en que administramos la memoria de manera efectiva.

¿Por qué tenemos nulo? ...

Los tipos de valor se almacenan en la pila, su valor se encuentra directamente en esa parte de la memoria (es decir, int x = 5 significa que la ubicación de la memoria para esa variable contiene " 5 ").

Por otro lado, los tipos de referencia tienen un " puntero " en la pila que apunta al valor actual en el montón (es decir, la cadena x = " ello " significa que el bloque de memoria en la pila solo contiene una dirección que apunta al valor real en el montón).

Un valor nulo simplemente significa que nuestro valor en la pila no apunta a ningún valor real en el montón, es un puntero vacío.

Espero que lo haya explicado bastante bien.

Si está obteniendo una 'NullReferenceException', quizás siga refiriéndose a objetos que ya no existen. Este no es un problema con 'nulo', es un problema con su código que apunta a direcciones inexistentes.

Hay situaciones en las que null es una buena forma de indicar que no se ha inicializado una referencia. Esto es importante en algunos escenarios.

Por ejemplo:

MyResource resource;
try
{
  resource = new MyResource();
  //
  // Do some work
  //
}
finally
{
  if (resource != null)
    resource.Close();
}

En la mayoría de los casos, esto se logra mediante el uso de una declaración using . Pero el patrón todavía se usa ampliamente.

Con respecto a su NullReferenceException, la causa de tales errores a menudo es fácil de reducir mediante la implementación de un estándar de codificación donde todos los parámetros se verifican para determinar su validez. Dependiendo de la naturaleza del proyecto, encuentro que en la mayoría de los casos es suficiente para verificar los parámetros de los miembros expuestos. Si los parámetros no están dentro del rango esperado, se lanza una ArgumentException de algún tipo, o se devuelve un resultado de error, según el patrón de manejo de errores en uso.

La comprobación de parámetros en sí misma no elimina errores, pero cualquier error que ocurra es más fácil de localizar y corregir durante la fase de prueba.

Como nota, Anders Hejlsberg ha mencionado la falta de aplicación no nula como una de los errores más grandes en la especificación C # 1.0 y que incluirlo ahora es " difícil " ;.

Si aún piensa que un valor de referencia no nulo aplicado de forma estática es de gran importancia, puede consultar idioma # de especificación . Es una extensión de C # donde las referencias que no son nulas son parte del lenguaje. Esto garantiza que a una referencia marcada como no nula nunca se le pueda asignar una referencia nula.

Una respuesta mencionó que hay nulos en las bases de datos. Eso es cierto, pero son muy diferentes de los nulos en C #.

En C #, los nulos son marcadores para una referencia que no se refiere a nada.

En las bases de datos, los nulos son marcadores para celdas de valor que no contienen un valor. Por celdas de valor, generalmente me refiero a la intersección de una fila y una columna en una tabla, pero el concepto de celdas de valor podría extenderse más allá de las tablas.

La diferencia entre los dos parece trivial, a primera vista. Pero no lo es.

Nulo, ya que está disponible en C # / C ++ / Java / Ruby, se ve mejor como una rareza de algún pasado oscuro (Algol) que de alguna manera sobrevivió hasta el día de hoy.

Lo usas de dos maneras:

  • Para declarar referencias sin inicializarlas (incorrectas).
  • Para denotar opcionalidad (OK).

Como ha adivinado, 1) es lo que nos causa un sinfín de problemas en los lenguajes imperativos comunes y debería haber sido prohibido hace mucho tiempo, 2) es la verdadera característica esencial.

Hay idiomas por ahí que evitan 1) sin prevenir 2).

Por ejemplo, OCaml es tal idioma.

Una función simple que devuelve un entero cada vez mayor desde 1:

let counter = ref 0;;
let next_counter_value () = (counter := !counter + 1; !counter);;

Y con respecto a la opcionalidad:

type distributed_computation_result = NotYetAvailable | Result of float;;
let print_result r = match r with
    | Result(f) -> Printf.printf "result is %f\n" f
    | NotYetAvailable -> Printf.printf "result not yet available\n";;

No puedo hablar de tu problema específico, pero parece que el problema no es la existencia de nulo. Nulo existe en las bases de datos, necesita alguna forma de tenerlo en cuenta en el nivel de la aplicación. No creo que esa sea la única razón por la que existe en .net, eso sí. Pero me imagino que es una de las razones.

Me sorprende que nadie haya hablado de bases de datos para su respuesta. Las bases de datos tienen campos que admiten nulos, y cualquier idioma que reciba datos de una base de datos debe manejar eso. Eso significa tener un valor nulo.

De hecho, esto es tan importante que para los tipos básicos como int, ¡puedes hacerlos anulables!

También considere los valores de retorno de las funciones, ¿qué sucedería si quisiera que una función dividiera un par de números y el denominador pudiera ser 0? El único " correcto " La respuesta en tal caso sería nula. (Sé que, en un ejemplo tan simple, una excepción probablemente sería una mejor opción ... pero puede haber situaciones en las que todos los valores sean correctos, pero los datos válidos pueden generar una respuesta no válida o no calculable. No estoy seguro de que se deba usar una excepción en tales casos ...)

Además de TODAS las razones ya mencionadas, NULL es necesario cuando necesita un marcador de posición para un objeto aún no creado. Por ejemplo. Si tiene una referencia circular entre un par de objetos, entonces necesita nulo ya que no puede crear una instancia de ambos simultáneamente.

class A {
  B fieldb;
}

class B {
  A fielda;
}

A a = new A() // a.fieldb is null
B b = new B() { fielda = a } // b.fielda isnt
a.fieldb = b // now it isnt null anymore

Editar: Es posible que pueda extraer un lenguaje que funcione sin nulos, pero definitivamente no será un lenguaje orientado a objetos. Por ejemplo, prólogo no tiene valores nulos.

Si crea un objeto con una variable de instancia como referencia a algún objeto, ¿qué valor sugeriría que tiene esta variable antes de asignarle alguna referencia de objeto?

Yo propongo:

  1. Prohibición nula
  2. Extender Booleanos: Verdadero, Falso y FileNotFound

Comúnmente - NullReferenceException significa que a algún método no le gustó lo que se entregó y devolvió una referencia nula, que luego se usó sin verificar la referencia antes de su uso.

Ese método podría haber lanzado una excepción más detallada en lugar de devolver el valor nulo, lo que cumple con el modo de pensamiento fail fast .

O el método podría devolverle un valor nulo como una conveniencia para usted, de modo que pueda escribir if en lugar de intentar y evitar el " sobrecarga " de una excepción.

  • Anulación explícita, aunque rara vez es necesario. Tal vez uno pueda verlo como una forma de programación defensiva.
  • Utilícelo (o estructura anulable (T) para no nulables) como indicador para indicar que falta un campo al asignar campos de un origen de datos (flujo de bytes, tabla de base de datos) a un objeto. Puede ser muy difícil hacer una bandera booleana para cada campo que pueda contener nulos, y puede ser imposible usar valores de centinela como -1 o 0 cuando todos los valores en el rango de ese campo son válidos. Esto es especialmente útil cuando hay muchos campos.

Si estos son casos de uso o abuso es subjetivo, pero los uso a veces.

Perdón por responder cuatro años tarde, me sorprende que ninguna de las respuestas haya respondido la pregunta original de esta manera:

Los lenguajes como C # y Java , como C y otros lenguajes anteriores a ellos, tienen null para que el programador pueda escribir rápidamente , código optimizado utilizando los punteros de una manera eficiente.


  • Vista de bajo nivel

Un poco de historia primero. La razón por la que se inventó null es por eficiencia. Al realizar la programación de bajo nivel en ensamblaje, no hay abstracción, tiene valores en los registros y desea aprovecharlos al máximo. Definir cero como un valor no es un puntero válido es una excelente estrategia para representar un objeto o nada .

¿Por qué desperdiciar la mayoría de los valores posibles de una palabra de memoria perfectamente buena, cuando puede tener una sobrecarga de cero memoria, una implementación realmente rápida del patrón de valor opcional ? Es por esto que null es tan útil.

  • Vista de alto nivel.

Semánticamente, null no es necesario para los lenguajes de programación. Por ejemplo, en los lenguajes funcionales clásicos como Haskell o en la familia ML, no hay nulo, sino tipos llamados Maybe u Option. Representan el concepto más alto de valor opcional sin preocuparse por el aspecto del código de ensamblaje generado (ese será el trabajo del compilador).

Y esto también es muy útil, ya que permite al compilador detectar más errores , y eso significa menos NullReferenceExceptions.

  • Reuniéndolos

A diferencia de estos lenguajes de programación de muy alto nivel, C # y Java permiten un posible valor nulo para todos los tipos de referencia (que es otro nombre para el tipo que terminará implementándose utilizando punteros ).

Esto puede parecer algo malo, pero lo bueno de esto es que el programador puede usar el conocimiento de cómo funciona bajo el capó, para crear un código más eficiente (aunque el lenguaje tiene recolección de basura).

Esta es la razón por la que null todavía existe en los idiomas hoy en día: un compromiso entre la necesidad de un concepto general de valor opcional y la necesidad siempre presente de eficiencia.

La función que no podría funcionar sin nulo es poder representar la ausencia de un objeto "

La ausencia de un objeto es un concepto importante. En la programación orientada a objetos, la necesitamos para representar una asociación entre objetos que es opcional: el objeto A se puede adjuntar a un objeto B, o A podría no tener un objeto B. Sin nulo, todavía podríamos emular esto: por ejemplo podríamos usar una lista de objetos para asociar B con A. Esa lista podría contener un elemento (una B), o estar vacía. Esto es algo inconveniente y realmente no soluciona nada. El código que asume que hay una B, como aobj.blist.first (). Method () va a explotar de manera similar a una excepción de referencia nula: (si se muestra blist está vacío, ¿cuál es el comportamiento de blist.first () ?

Hablando de listas, null le permite terminar una lista enlazada. Un ListNode puede contener una referencia a otro ListNode que puede ser nulo. Lo mismo puede decirse acerca de otras estructuras de conjuntos dinámicos, como los árboles. Null le permite tener un árbol binario ordinario cuyos nodos de hoja están marcados por tener referencias secundarias nulas.

Las listas y los árboles se pueden construir sin nulo, pero tienen que ser circulares, o bien infinitos / perezosos. Probablemente, la mayoría de los programadores considerarán una restricción inaceptable, ya que preferirían tener opciones en el diseño de estructuras de datos.

Los problemas asociados con las referencias nulas, como las referencias nulas que surgen accidentalmente debido a errores y causan excepciones, son parcialmente una consecuencia del sistema de tipo estático, que introduce un valor nulo en cada tipo: hay una cadena nula, un entero nulo, Widget nulo, ...

En un lenguaje de tipo dinámico, puede haber un solo objeto nulo, que tiene su propio tipo. El resultado de esto es que tiene todas las ventajas representativas de nulo, además de una mayor seguridad. Por ejemplo, si escribe un método que acepta un parámetro String, entonces tiene la garantía de que el parámetro será un objeto de cadena y no nulo. No hay referencia nula en la clase String: algo que se sabe que es una cadena no puede ser el objeto nulo. Las referencias no tienen tipo en un lenguaje dinámico. Una ubicación de almacenamiento, como un miembro de clase o un parámetro de función, contiene un valor que puede ser una referencia a un objeto. Ese objeto tiene tipo, no la referencia.

Por lo tanto, estos idiomas proporcionan un modelo limpio, más o menos matemáticamente puro de "null", y luego los estáticos lo convierten en un monstruo de Frankenstein.

Si un marco permite la creación de una matriz de algún tipo sin especificar qué se debe hacer con los nuevos elementos, ese tipo debe tener algún valor predeterminado. Para los tipos que implementan semántica de referencia mutable (*), en el caso general no hay un valor predeterminado razonable. Considero que es una debilidad del marco .NET que no hay forma de especificar que una llamada de función no virtual debe suprimir cualquier comprobación nula. Esto permitiría que tipos inmutables como String se comporten como tipos de valor, devolviendo valores razonables para propiedades como Length.

(*) Tenga en cuenta que en VB.NET y C #, la semántica de referencia mutable puede implementarse por clase o por tipo de estructura; un tipo de estructura implementaría una semántica de referencia mutable al actuar como un proxy para una instancia envuelta de un objeto de clase al que tiene una referencia inmutable.

También sería útil si se pudiera especificar que una clase debería tener una semántica de tipo de valor mutable no anulable (lo que implica que, como mínimo, la creación de una instancia de un campo de ese tipo crearía una nueva instancia de objeto utilizando un constructor predeterminado , y que al copiar un campo de ese tipo se crearía una nueva instancia al copiar la anterior (manejando recursivamente cualquier clase de valor anidada).

No está claro, sin embargo, exactamente la cantidad de soporte que se debe incorporar en el marco para esto. Si el marco reconoce las diferencias entre los tipos de valores mutables, los tipos de referencia mutables y los tipos inmutables, permitiría que las clases que a su vez hacen referencia a una mezcla de tipos mutables e inmutables de clases externas eviten realizar copias innecesarias de objetos inmutables.

El nulo es un requisito esencial de cualquier lenguaje OO. Cualquier variable de objeto a la que no se le haya asignado una referencia de objeto debe ser nula.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top