En C #, ¿qué sucede cuando llama a un método de extensión en un objeto nulo?

329

¿Se llama al método con un valor nulo o da una excepción de referencia nula?

MyObject myObject = null;
myObject.MyExtensionMethod(); // <-- is this a null reference exception?

Si este es el caso, ¿nunca tendré que verificar mi parámetro 'this' para que sea nulo?

tpower
fuente
A menos que, por supuesto, se trate de ASP.NET MVC que arrojará este error Cannot perform runtime binding on a null reference.
Mrchief

Respuestas:

387

Eso funcionará bien (sin excepción). Los métodos de extensión no usan llamadas virtuales (es decir, usa la instrucción "call" il, no "callvirt"), por lo que no hay verificación nula a menos que lo escriba usted mismo en el método de extensión. Esto es realmente útil en algunos casos:

public static bool IsNullOrEmpty(this string value)
{
    return string.IsNullOrEmpty(value);
}
public static void ThrowIfNull<T>(this T obj, string parameterName)
        where T : class
{
    if(obj == null) throw new ArgumentNullException(parameterName);
}

etc.

Básicamente, las llamadas a llamadas estáticas son muy literales, es decir

string s = ...
if(s.IsNullOrEmpty()) {...}

se convierte en:

string s = ...
if(YourExtensionClass.IsNullOrEmpty(s)) {...}

donde obviamente no hay cheque nulo.

Marc Gravell
fuente
1
Marc, estás hablando de llamadas "virtuales", pero lo mismo es cierto para las llamadas no virtuales en los métodos de instancia. Creo que la palabra "virtual" aquí está fuera de lugar.
Konrad Rudolph
66
@ Konrad: Depende del contexto. El compilador de C # generalmente usa callvirt incluso para métodos no virtuales, precisamente para obtener una verificación nula.
Jon Skeet
Me refería a la diferencia entre call y callvirt il instrucciones. En una edición, en realidad intenté href las dos páginas de Opcodes, pero el editor vomitó en los enlaces ...
Marc Gravell
2
No veo cómo este uso de los métodos de extensión puede ser realmente útil. El hecho de que se pueda hacer no significa que sea correcto, y como Binary Worrier menciona a continuación, me parece más una aberración por decir lo menos.
Trampa
3
@Trap: esta función es excelente si te gusta la programación de estilo funcional.
Roy Tinker
50

Además de la respuesta correcta de Marc Gravell.

Podría obtener una advertencia del compilador si es obvio que este argumento es nulo:

default(string).MyExtension();

Funciona bien en tiempo de ejecución, pero produce la advertencia "Expression will always cause a System.NullReferenceException, because the default value of string is null".

Stefan Steinegger
fuente
32
¿Por qué advertiría "siempre causa una System.NullReferenceException". Cuando, de hecho, nunca lo hará?
tpower
46
Afortunadamente, a los programadores solo nos importan los errores, no las advertencias: p
JulianR
77
@JulianR: Sí, algunos lo hacen, otros no. En nuestra configuración de compilación de versiones, tratamos las advertencias como errores. Entonces simplemente no funciona.
Stefan Steinegger
8
Gracias por la nota; Lo pondré en la base de datos de errores y veremos si podemos solucionarlo para C # 4.0. (Ninguna promesa - ya que es un caso límite realista y simplemente una advertencia, que podría batea en la fijación de la misma.)
Eric Lippert
3
@Stefan: Dado que es un error y no una advertencia "verdadera", puede usar una instrucción #pragma para suprimir la advertencia y hacer que el código pase su versión de lanzamiento.
Martin RL
25

Como ya ha descubierto, dado que los métodos de extensión son simplemente métodos estáticos glorificados, se llamarán con nullreferencias pasadas, sin que NullReferenceExceptionse arrojen. Pero, como parecen ser métodos de instancia para la persona que llama, también deben comportarse como tales. Entonces, la mayoría de las veces, debe verificar el thisparámetro y lanzar una excepción si es así null. Está bien no hacer esto si el método se ocupa explícitamente de los nullvalores y su nombre lo indica debidamente, como en los ejemplos a continuación:

public static class StringNullExtensions { 
  public static bool IsNullOrEmpty(this string s) { 
    return string.IsNullOrEmpty(s); 
  } 
  public static bool IsNullOrBlank(this string s) { 
    return s == null || s.Trim().Length == 0; 
  } 
}

También escribí una publicación de blog sobre esto hace algún tiempo.

Jordão
fuente
3
Lo voté porque es correcto y tiene sentido para mí (y está bien escrito), mientras que también prefiero el uso descrito en la respuesta por @Marc Gravell.
qxotk
17

Se pasará un valor nulo al método de extensión.

Si el método intenta acceder al objeto sin verificar si es nulo, entonces sí, arrojará una excepción.

Un tipo aquí escribió los métodos de extensión "IsNull" e "IsNotNull" que comprueban si la referencia pasó nula o no. Personalmente, creo que esto es una aberración y no debería haber visto la luz del día, pero es perfectamente válido c #.

Binario Worrier
fuente
18
De hecho, para mí es como preguntarle a un cadáver "¿Estás vivo" y obtener una respuesta de "no". Un cadáver no puede responder a ninguna pregunta, tampoco debe poder "llamar" a un método en un objeto nulo.
Binario Worrier
14
No estoy de acuerdo con la lógica de Binary Worrier, ya que es útil poder llamar extensiones sin preocuparse por referencias nulas, pero +1 por valor de comedia de analogía :-)
Tim Abell
10
En realidad, a veces no se sabe si alguien está muerto, por lo que todavía se pregunta, y la persona puede responder, "no, simplemente descansando con los ojos cerrados"
nurchi
77
Cuando necesite encadenar varias operaciones (digamos 3+), puede (suponiendo que no haya efectos secundarios) convertir varias líneas de código aburrido de verificación nula en una línea elegante encadenada con métodos de extensión "null-safe". (Similar al operador sugerido ".?", Pero ciertamente no es tan elegante.) Si no es obvio que una extensión es "nula-segura", generalmente prefijo el método con "Seguro", así que si, por ejemplo, es una copia- método, su nombre podría ser "SafeCopy" y devolvería nulo si el argumento fuera nulo.
AnorZaken
3
Me reí tanto con la respuesta de @BinaryWorrier jajajaja me vi pateando un cuerpo para verificar si estaba muerto o no jajaja Así que, en mi imaginación, quién verificó si el cuerpo estaba muerto o no era yo y no el cuerpo mismo, la implementación para el control estaba en mí, pateándolo activamente para ver si se movía. Entonces, un cuerpo no sabe si está muerto o no, la OMS lo verifica, sabe, ahora podría argumentar que podría "enchufar" al cuerpo una forma para que le diga si está muerto o no y que, en mi opinión, es lo que una extensión es para.
Zorkind
7

Como otros señalaron, llamar a un método de extensión en referencia nula hace que este argumento sea nulo y no suceda nada más. Esto da lugar a la idea de utilizar métodos de extensión para escribir cláusulas de protección.

Puede leer este artículo para ver ejemplos: Cómo reducir la complejidad ciclomática: cláusula de protección La versión corta es esta:

public static class StringExtensions
{
    public static void AssertNonEmpty(this string value, string paramName)
    {
        if (string.IsNullOrEmpty(value))
            throw new ArgumentException("Value must be a non-empty string.", paramName);
    }
}

Este es el método de extensión de clase de cadena que se puede invocar en referencia nula:

((string)null).AssertNonEmpty("null");

La llamada funciona bien solo porque el tiempo de ejecución llamará con éxito al método de extensión con referencia nula. Luego puede usar este método de extensión para implementar cláusulas de protección sin una sintaxis desordenada:

    public IRegisteredUser RegisterUser(string userName, string referrerName)
    {

        userName.AssertNonEmpty("userName");
        referrerName.AssertNonEmpty("referrerName");

        ...

    }
Zoran Horvat
fuente
3

El método de extensión es estático, por lo que si no hace nada en este MyObject, no debería ser un problema, una prueba rápida debería verificarlo :)

Fredrik Leijon
fuente
-1

Hay pocas reglas de oro cuando quieres que tu sea legible y vertical.

  • Vale la pena decir que Eiffel dice que el código específico encapsulado en un método debería funcionar en contra de alguna entrada, que el código es viable si se cumplen algunas condiciones previas y aseguran una salida esperada.

En su caso, DesignByContract está roto ... va a realizar algo de lógica en una instancia nula.

ruslander
fuente