2011-02-22 21 views
7

Al escribir una clase que contiene una variable genérica¿Por qué puedo probar un genérico para nulo cuando puede no ser anulable o puede no ser un objeto?

public class ServiceInvoker<TService> : IDisposable 
{ 
    private TService _Service; 

    public ServiceInvoker() 
    { 
     _Service = Activator.CreateInstance<TService>(); 
    } 

    public void Invoke(Action<TService> action) 
    { 
     // CAN use null 
     if (_Service == null) 
      throw new ObjectDisposedException("ServiceInvoker"); 

     .... 
    } 

    public void Dispose() 
    { 
     // CAN'T use null 
     this._Service = default(TService); 
    } 
} 

Noté que el compilador me permite comprobar NULL en una variable genérica, sino que, por supuesto, no permiten que me puse a null , de ahí que tengamos que usar default(TService).

¿No debería el compilador advertirme que uso null? ¿O está usando la conversión de boxeo a un objeto para hacer una prueba de nulo aquí?

He leído sobre el proper null evaluation for generic, pero me interesa saber por qué, en lugar de cómo.

+0

Visual Studio 2012 pone una línea ondulada debajo del operador == y dice "Posible comparación del tipo de valor con nulo". –

Respuesta

11

¿Por qué puedo probar un genérico para nulo cuando puede no ser anulable o no ser un objeto?

La pregunta está mal planteada; el valor de cualquier expresión de tipo genérico siempre será un objeto o una referencia nula en tiempo de ejecución. Creo que quería preguntar:

¿Por qué puedo probar un genérico para nulo cuando su tipo de tiempo de ejecución podría no ser ni un tipo de valor que admite nulos ni un tipo de referencia?

El C# garantías de especificación que la comparación de igualdad frente a la nula literal es legal en la sección 7.6.10, que cito aquí para su conveniencia:


Si un operando de un tipo de parámetro de tipo T se compara con nulo, y el tipo de tiempo de ejecución de T es un tipo de valor, el resultado de la comparación es falso. [...] La construcción x == null está permitida aunque T podría representar un tipo de valor, y el resultado simplemente se define como falso cuando T es un tipo de valor.


Tenga en cuenta que hay un pequeño error en la especificación aquí; la última oración debe terminar "tipo de valor no admitible".

Nunca estoy seguro de haber respondido realmente un "¿por qué?" pregunta satisfactoriamente o no. Si eso no es satisfactorio, intente hacer una pregunta más específica.

¿No debería el compilador advertirme que uso null?

No. ¿Por qué cree que el compilador debería advertirle sobre el uso correcto de una característica del idioma?

¿Está usando la conversión de boxeo a un objeto para hacer una prueba de nulo aquí?

Bueno, eso es un punto delicado. Por un lado, la sección 7.6.10 estados:


Los operadores de igualdad de tipo de referencia predefinidos nunca causan las operaciones de boxeo que se produzca por sus operandos. No tendría sentido realizar tales operaciones de boxeo, ya que las referencias a las nuevas instancias en caja asignadas necesariamente diferirían de todas las otras referencias.


Sin embargo, cuando generamos IL para la comparación de una T genérica a null, que, por supuesto, en realidad sí generan una caja de T, una carga de nulo, y una comparación. El jitter puede ser lo suficientemente inteligente como para eludir la asignación real de memoria del boxeo; si T es un tipo de referencia, no necesita boxeo; si es un tipo de valor que admite valores NULL, se puede convertir en una llamada para verificar la propiedad HasValue, y si se trata de un tipo de valor que no admite nulos, el boxeo y la verificación pueden ser convertido en simplemente generar "falso". No sé exactamente qué hacen las diferentes implementaciones del compilador jit; si estás interesado, ¡mira y mira!

+0

Bueno, un por qué puede responderse satisfactoriamente indicando la especificación como lo hizo. Muchas gracias por tomarse el tiempo para responder.En cuanto a la advertencia, pensé que sí, una advertencia podría ser útil, pero no es necesaria. Pude ver que mi código no estaba bien al leerlo, así que una pequeña revisión me ayudó a solucionar un posible problema. Después de arreglarlo, pensé que era raro que pudiera compararlo con nulo, así que pensé en preguntar. –

+1

@ Pierre-Alain: Feliz de ayudar. La gente a menudo responde "porque la especificación lo dice" con "pero, ¿por qué lo dice la especificación?" Y luego digo "porque los autores de las especificaciones pensaron que era una buena idea" Y luego dices "pero * ¿por qué * pensaron que era una buena idea?" y luego digo "los beneficios superan los costos" y luego dices "¿cuáles son todos los costos y todos los beneficios y cómo los comparas?" y eso podría llevar horas explicarlo y plantear aún más preguntas. "Por qué" las preguntas son difíciles! –

+1

Gran conversación: O Sin embargo, en la última pregunta, cuando usted dijo "¿cuáles son todos los costos y todos los beneficios y cómo los compara?", ¿Están todos los costos y beneficios documentados también? (Ya que solo contribuyen a la decisión final, por lo tanto, es posible que no se conserven en el registro). Pero supongo que se basa en lo que las personas que estuvieron involucradas en ese momento pueden recordar, ¿no? –

4

El CLR sabe que un tipo de valor que no admite nulos nunca puede ser nulo, por lo que probablemente siempre devuelva false. ¿Es eso lo que estás preguntando?

+0

Luego es un condicional 'always return false', porque si TService es una clase, entonces funcionará, pero si, por alguna razón, TService es una estructura, devolverá false sin importar qué. Eso obviamente romperá el comportamiento de mi clase dependiendo de cómo se use. –

+2

@Pierre, puedes comparar las estructuras con valores nulos todo el día. 'bool worthless = (1 == null);' es perfectamente legal. Para su código, podría simplemente tener un miembro 'isDisposed' que establezca cuando su objeto sea eliminado. Puede verificar ese miembro internamente en lugar de comparar su objeto 'TService' con el predeterminado (el valor predeterminado puede estar en un estado perfectamente válido). –

+0

@Anthony Buena idea acerca de cambiar el cheque a nulo a un booleano. Solo hice eso. –

1

Esto compila, aunque con una advertencia:

int l = 0; 
if (l == null) { 
    //whatever 
} 

La advertencia es la siguiente:

warning CS0472: The result of the expression is always 'false' 
since a value of type 'int' is never equal to 'null' of type 'int?' 

por lo que funciona, aunque con resultados no muy útiles. Sin embargo, con un genérico, podría ser una prueba útil.

+0

Pero preguntó específicamente sobre los genéricos, todos sabemos que con un tipo int siempre obtenemos esa advertencia. – Aidiakapi

+1

Mi punto es que solo es una * advertencia * cuando se usa de esa manera, no es un * error *. Si fue un error hacer esa comparación, entonces esperaría que la comparación con un genérico no restringido fuera una advertencia/error. Pero dado que no lo es, parece ser una comparación válida (¿aunque útil?). –

+0

El compilador JIT es lo suficientemente inteligente como para ignorar las comprobaciones nulas que se realizan en tipos de valor nulo. Es por eso que es perfectamente legal usarlo aunque sea inútil. Con los genéricos puede haber un caso de uso porque no conoce el tipo de antemano. –

1

¿Por qué crees que el compilador debería te avisa? El compilador dará advertencias cuando está usando algo como esto:

if (1 == 2) //always false 
if (Dog is Cat) //never true 

Pero en este caso, la condición es posible ser verdad (cuando TService es un tipo refernece y es nulo), o falsas (cuando se trata de una tipo de valor, o un tipo de referencia pero no nulo). Representa perfectamente el uso de si la declaración (a veces verdadera, y algunas veces falsa).

Cuestiones relacionadas