2008-12-15 9 views
51

Supongamos que tengo un método que toma un objeto de algún tipo como argumento. Ahora diga que si a este método se le pasa un argumento nulo, es un error fatal y se debe lanzar una excepción. ¿Vale la pena para mí para codificar algo como esto (teniendo en cuenta que es un ejemplo trivial):Throwing ArgumentNullException

void someMethod(SomeClass x) 
{ 
    if (x == null){ 
     throw new ArgumentNullException("someMethod received a null argument!"); 
    } 

    x.doSomething(); 
} 

O es seguro para mí sólo se basan en él lanzando NullException cuando llama x.doSomething() ?

En segundo lugar, supongamos que someMethod es un constructor y x no se usará hasta que se llame a otro método. ¿Debería lanzar la excepción inmediatamente o esperar hasta que se necesite x y arrojar la excepción, entonces?

+1

Usted puede estar interesado en el método Throw.If explicado por los servicios públicos [10 desarrolladores de C# deben saber] (http://www.codeducky.org/10-utilities-c-developers-should-know-part -one /) y Throw.IfNull en [10 utilidades gist en github] (https://gist.github.com/madelson/9177264). Le permiten escribir su lógica de comprobación de argumentos de manera más sucinta: 'Throw.IfNull (x, '' someMethod recibió un argumento nulo!")' –

+3

También vale la pena señalar que el constructor de 'ArgumentNullException' que está utilizando aquí no toma un mensaje completo como parámetro. En su lugar, toma' paramName', que es el nombre del argumento que era nulo Vea [MSDN para obtener la definición completa] (http://msdn.microsoft.com/en-us/library/wssa019h (v = vs.110) .aspx). –

+0

http://programmers.stackexchange.com/questions/121121/how-does-throwing-an-argumentnullexception-help –

Respuesta

56

Prefiero el ArgumentNullException sobre el NullReferenceException que no verificaría el argumento. En general, mi preferencia es verificar siempre la nulidad antes de intentar invocar un método en un objeto potencialmente nulo.

Si el método es un constructor, dependerá de un par de factores diferentes: ¿existe también un organismo público para la propiedad y qué tan probable es que el objeto se use realmente? Si hay un organismo público, entonces no proporcionar una instancia válida a través del constructor sería razonable y no debería dar lugar a una excepción.

Si no hay un organismo público y es posible usar el objeto contenedor sin referencia al objeto inyectado, es posible que desee diferir la comprobación/excepción hasta que se intente su uso. Sin embargo, creo que el caso general sería que el objeto inyectado es esencial para el funcionamiento de la instancia y, por lo tanto, una excepción ArgumentNull es perfectamente razonable ya que la instancia no puede funcionar sin ella.

+4

+1, NullReferenceException es malo – user7116

+8

Sí, no es una cuestión de preferencia. ArgumentNullException es correcto y NullReference La excepción no es NullReferenceException es para cuando un objeto nulo es al que se accede, como un campo o propiedad del mismo. – JamesBrownIsDead

+0

+1. Puede usar [contratos de código] [1] para minimizar la sobrecarga de código de 'ArgumentNullException': ' Contract.Requiere (x! = Null, nameof (x)); ' Creo que este es el camino más corto sin gastos indirectos de tiempo de ejecución significativos. Todavía tiene la referencia duplicada a x pero ese es el único inconveniente. [1]: https://docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/code-contracts – Marcel

28

Siempre sigo la práctica de fail fast. Si su método depende de X y entiende que X podría pasarse en nulo, nírelo e inicie la excepción de inmediato en lugar de prolongar el punto de falla.

2016 actualización:

ejemplo del mundo real. Recomiendo encarecidamente el uso de Jetbrains Annotations.

[Pure] 
public static object Call([NotNull] Type declaringType, 
          [NotNull] string methodName, 
          [CanBeNull] object instance) 
{ 
    if (declaringType == null) throw new ArgumentNullException(nameof(declaringType)); 
    if (methodName == null) throw new ArgumentNullException(nameof(methodName)); 

declaraciones Guardia se han mejorado enormemente con C# 6 proporciona al operador nameof.

13

prefiero la excepción explícita, por estas razones:

  • Si el método tiene más de un argumento SomeClass le da la oportunidad de decir cuál es (todo lo demás está disponible en la pila de llamadas) .
  • ¿Qué sucede si hace algo que pueda tener un efecto secundario antes de hacer referencia a x?
3

Si programa a la defensiva, debe fallar rápidamente. Así que revisa tus entradas y error al principio de tu código. Debe ser amable con su interlocutor y darles el mensaje de error más descriptivo que pueda.

5

Es mejor lanzar ArgumentNullException más pronto que tarde. Si lo arroja, puede proporcionar más información útil sobre el problema que una NullReferenceException.

4

1- Hazlo explícitamente si no quieres valor nulo. De lo contrario, cuando otra persona busque su código, ellos creerán que se acepta pasar un valor nulo.

2- Hazlo lo más rápido que puedas. De esta forma, no se propaga el comportamiento "incorrecto" de tener un valor nulo cuando no se supone.

5

Debería lanzar explícitamente una ArgumentNullException si está esperando que la entrada no sea nula. Es posible que desee escribir una clase llamada Guard que proporciona métodos de ayuda para esto. Por lo que su código será:

void someMethod(SomeClass x, SomeClass y) 
{ 
    Guard.NotNull(x,"x","someMethod received a null x argument!"); 
    Guard.NotNull(y,"y","someMethod received a null y argument!"); 


    x.doSomething(); 
    y.doSomething(); 
} 

El método no nulo haría la verificación de nulidad y lanzar una NullArgumentException con el mensaje de error especificado en la llamada.

+5

¿Qué tal el stacktrace en la excepción? Contendría el método Guard NotNull, que es solo ruido y podría causar confusión. ¿Hay alguna forma de evitar esto? – khebbie

+1

@khebbie 'try {Guard.NotNull (...); ...} catch (Excepción ex) {throw ex; } ' Solo asegúrate de NO volver a lanzarlo usando' throw; 'sin argumentos, o no se sobrescribirá su traza de pila. – Zenexer

+0

+1 Me gusta ese enfoque de "Guardia". La mejor legibilidad del código supera el poco ruido en el stacktrace imo. Incluso me gustaría hacer guardia de una subclase de ArgumentNullException como MyArgumentException.ThrowIfNull (x, "x", "recibió un argumento nulo x!") – Sebastian

8

Preferiría la verificación de parámetros con la explícita ArgumentNullException, también.

En cuanto a los metadatos:

// 
    // Summary: 
    //  Initializes a new instance of the System.ArgumentNullException class with 
    //  the name of the parameter that causes this exception. 
    // 
    // Parameters: 
    // paramName: 
    //  The name of the parameter that caused the exception. 
    public ArgumentNullException(string paramName); 

Se puede ver, que la cadena debe ser el nombre del parámetro, que es nulo, y así darle al desarrollador una pista sobre lo que va mal.

+0

Gracias! Supongo que debería haber pasado más tiempo leyendo la documentación sobre el constructor. : -/ –

9

Estoy de acuerdo con la idea de fallar rápido, sin embargo, es conveniente saber por qué fallar rápido es práctico. Considere este ejemplo:

void someMethod(SomeClass x) 
{  
    x.Property.doSomething(); 
} 

Si depende de la NullReferenceException para decirle que algo estaba mal, ¿cómo vas a saber lo que era nulo? La traza de la pila solo le dará un número de línea, no la referencia fue nula. En este ejemplo, x o x.Property podrían haber sido nulos y sin fallar rápidamente con una verificación agresiva de antemano, no sabrá cuál es.

+0

De acuerdo ... Si solo C# tuviera el operador nulo de desfinanciamiento seguro, entonces tendríamos algo como x? .Property? .doSomething(); En ese caso, la propiedad solo se evaluaría si x no fuera nula, etc. – Anastasiosyal

+1

@Anastasiosyal ¡Oye, C# ** no tiene ** esto ahora, casi 4 años después! – JimmyBoh

+0

Sí, por fin, ¡está aquí! Felices días :) – Anastasiosyal

0

Probablemente estaré desilusionada por esto, pero creo que es completamente diferente.

¿Qué hay de seguir una buena práctica llamada "never pass null" y eliminar la fea excepción de comprobación?

Si el parámetro es un objeto, NO PASA NULO. Además, NO DEVUELVA NULL. Incluso puede usar el patrón de objeto nulo para ayudar con eso.

Si es opcional, use los valores predeterminados (si su idioma los admite) o cree una sobrecarga.

Mucho más limpio que desagradables excepciones.

+0

No sabe quién podría llamar a su método algún día en el futuro mucho tiempo después de haber cambiado de trabajo, o cuántas capas ha atravesado un objeto antes de llegar a su método. Confiar en que su método no reciba nulo no es una opción viable ya que está fuera de su control. Lanzar una excepción significativa está bajo su control. –

+0

"No sabe quién podría estar llamando a su método algún día en el futuro mucho tiempo después de haber cambiado de trabajo" <- entonces tiene un problema de comunicación, no de código. Cuanto menos código, mejor. Para mí, usar la comprobación de argumentos brinda beneficios solo cuando necesitas fallar rápidamente. De lo contrario, no pase nulo. Y, por cierto, habrá una excepción en algún lugar de todos modos. – andrecarlucci

+1

Diría que siempre necesita fallar rápido y asegurarse de que las excepciones sean significativas.Supongo que si ningún tercero tiene que usar tu código y trabajas en un equipo pequeño en la misma sala, puedes ser menos estricto con el manejo de errores. Pero eso es bastante circunstancial. En cualquier caso, creo que esto es más un tema para meta. –

0

Puede utilizar la sintaxis como la siguiente para no solo arrojar un ArgumentNullException, sino que tenga esa excepción para nombrar el parámetro como parte de su texto de error también. P.ej.;

void SomeMethod(SomeObject someObject) 
{ 
    Throw.IfArgNull(() => someObject); 
    //... do more stuff 
} 

La clase utilizada para levantar la excepción es;

public static class Throw 
{ 
    public static void IfArgNull<T>(Expression<Func<T>> arg) 
    { 
     if (arg == null) 
     { 
      throw new ArgumentNullException(nameof(arg), "There is no expression with which to test the object's value."); 
     } 

     // get the variable name of the argument 
     MemberExpression metaData = arg.Body as MemberExpression; 
     if (metaData == null) 
     { 
      throw new ArgumentException("Unable to retrieve the name of the object being tested.", nameof(arg)); 
     } 

     // can the data type be null at all 
     string argName = metaData.Member.Name; 
     Type type = typeof(T); 
     if (type.IsValueType && Nullable.GetUnderlyingType(type) == null) 
     { 
      throw new ArgumentException("The expression does not specify a nullible type.", argName); 
     } 

     // get the value and check for null 
     if (arg.Compile()() == null) 
     { 
      throw new ArgumentNullException(argName); 
     } 
    } 
} 
+0

Esta solución crea una ** sobrecarga de tiempo de ejecución considerable **, por lo que no puedo recomendarlo. – Marcel

+0

@Marcel, "considerable" es relativo. Para la mayoría de las situaciones de negocio/capa de dominio, está bien, no hay retraso notable (en menos de milisegundos). Para una pieza de código de alto rendimiento? Intenta evitar 'try/catch' por completo, ya que también tiene una sobrecarga de tiempo de ejecución muy alta. Es un caso de uso de lo que necesita, donde lo necesita. – DiskJunky

+0

@DiskJunkey: No estoy hablando de la parte de la excepción. Esto está bien. En la mayoría de las implementaciones, try/catch no causa una sobrecarga de tiempo de ejecución significativa a menos que se genere una excepción. La parte costosa es 'Expression >' que hace que el código de tiempo de ejecución cree un nuevo árbol de expresiones antes de cada invocación de 'IfArgNull', incluso si todo está bien y el argumento no es nulo. – Marcel

Cuestiones relacionadas