2010-01-07 15 views
32

Duplicar posible:
C# okay with comparing value types to nullestructuras Comparando a NULL

yo estaba trabajando en una aplicación de Windows en un entorno multiproceso y me veces obtener la excepción "invocar o BeginInvoke no puede ser llamado en un control hasta que se haya creado el identificador de ventana ". Así que pensé que simplemente agregaría esta línea de código:

if(this.Handle != null) 
{ 
    //BeginInvokeCode 
} 

Pero eso no solucionó el problema. Así que profundicé un poco más y me di cuenta de que IntPtr (el tipo que es Form.Handle) es una estructura que no puede ser anulada. Esta fue la solución que funcionó:

if(this.Handle != IntPtr.Zero) 
{ 
    //BeginInvokeCode 
} 

Entonces me di cuenta, ¿por qué incluso compilar cuando me marchaba por nula? Así que decidí probar en mi misma:

public struct Foo { } 

y luego:

static void Main(string[] args) 
    { 
     Foo f = new Foo(); 
     if (f == null) { } 
    } 

y lo suficientemente seguro de que no recogió diciendo que "un error del operador 1 '==' no se puede aplicar a operandos de tipo 'ConsoleApplication1.Foo' y '' ". Bien, entonces comencé a buscar los metadatos para IntPtr y comencé a agregar todo a mi estructura Foo que estaba allí en la estructura IntPtr (ISerializable, ComVisible) pero nada ayudó. ! Por último, cuando he añadido la sobrecarga de operadores de == y =, funcionó:

[Serializable] 
[ComVisible(true)] 
public struct Foo : ISerializable 
{ 
    #region ISerializable Members 

    public void GetObjectData(SerializationInfo info, StreamingContext context) 
    { 
     throw new NotImplementedException(); 
    } 

    #endregion 

    public override bool Equals(object obj) 
    { 
     return base.Equals(obj); 
    } 

    public override int GetHashCode() 
    { 
     return base.GetHashCode(); 
    } 

    public static bool operator ==(Foo f1, Foo f2) { return false; } 
    public static bool operator !=(Foo f1, Foo f2) { return false; } 
} 

Esto finalmente compiló:

static void Main(string[] args) 
    { 
     Foo f = new Foo(); 
     if (f == null) { } 
    } 

Mi pregunta es ¿por qué? ¿Por qué si anulas == y! = ¿Puedes comparar con nulo? Los parámetros para == y! = Son todavía del tipo Foo que no son nulables, entonces, ¿por qué se permite esto de repente?

+2

+1 para una buena pregunta por cierto, y hola de su compañero de Brooklyn :) –

Respuesta

14

Parece que el problema es que cuando MS introdujo tipos anulables, lo hicieron de manera que cada estructura es implícitamente convertible a su tipo anulable (foo?), por lo que el código de

if(f == null) 

es equivalente a

if ((Nullable<foo>)f == (Nullable<foo>)null) 

Desde MSDN establece que "todos los operadores definidos por el usuario que existen para los tipos de valores también pueden ser utilizados por los tipos anulables", cuando se reemplaza operator==, que permiten que la conversión implícita para compilar, ya que ahora tienen una definida por el usuario == - dándote la sobrecarga que admite nulos de forma gratuita.

Un aparte:

Parece que en tu ejemplo, hay una cierta optimización del compilador Lo único que se emite por el compilador que incluso insinúa que había una prueba es ésta IL:

ldc.i4.0 
ldc.i4.0 
ceq 
stloc.1 //where there is an unused boolean local 

Tenga en cuenta que si cambia el principal a

Foo f = new Foo(); 
object b = null; 
if (f == b) { Console.WriteLine("?"); } 

Ya no se compila. Pero si la casilla estructura:

Foo f = new Foo(); 
object b = null; 
if ((object)f == b) { Console.WriteLine("?"); } 

si compila, emite IL, y funciona como se esperaba (la estructura no es nulo);

2

Creo que cuando sobrecarga un operador, se suscribe explícitamente a la noción de que manejará toda la lógica necesaria con el operador específico. Por lo tanto, es su responsabilidad manejar nulo en el método de sobrecarga del operador, si alguna vez recibe un golpe. En este caso, como estoy seguro, probablemente hayas notado que los métodos sobrecargados nunca son golpeados si los comparas con nulos.

Lo que es realmente interesante es que después de Henks answer here, he comprobado el siguiente código en el reflector.

Foo f1 = new Foo(); 
if(f1 == null) 
{ 
    Console.WriteLine("impossible"); 
} 

Console.ReadKey(); 

Esto es lo que refleja el reflector.

Foo f1 = new Foo(); 
Console.ReadKey(); 

El compilador lo limpia y, por lo tanto, los métodos de operador sobrecargados nunca se llaman.

+0

... excepto, por supuesto, que un parámetro struct nunca será nulo, de ahí su pregunta. –

+0

correcto, pero no creo que el compilador se ocupe de ello, lo que significa que, en lo que respecta al compilador, usted sobrecargó al operador. Además, el método nunca se llama. –

1

estructura no define las sobrecargas "==" o "! =" por eso tienes el error original. Una vez que las sobrecargas se agregaron a su estructura, la comparación fue legal (desde un compilador prospectivo). Como creador de la sobrecarga del operador es su responsabilidad manejar esta lógica (obviamente, Microsoft omitió esto en este caso).

Dependiendo de la implementación de su estructura (y lo que representa) una comparación con null puede ser perfectamente válida y por eso es posible.

+0

No, no nos perdimos esta. (Aunque el hecho de que no se informa una advertencia parece ser un error.) Este comportamiento es correcto según la especificación. –

+0

@Eric, es bueno saberlo. Gracias –

3

Todo lo que puedo pensar es que su sobrecarga del operador == da el compilador una elección entre:

public static bool operator ==(object o1, object o2) 

y

public static bool operator ==(Foo f1, Foo f2) 

y que con tanto para elegir que es capaz de lanza la izquierda para objetar y usa la anterior. Ciertamente, si intenta ejecutar algo en función de su código, no entra en la sobrecarga de su operador. Sin elección entre operadores, el compilador está llevando a cabo algunas comprobaciones adicionales.

+0

Tenga en cuenta que sin la sobrecarga, el primer formulario todavía sería aplicable, pero el compilador lo prohíbe. Creo que ciertamente estás en la línea correcta, pero aquí hay magia más profunda. –

+2

La elección es en realidad entre (obj, obj), (Foo, Foo) y (Foo ?, Foo?) - la definición de un operador de igualdad en (Foo, Foo) también define automáticamente la versión elevada a nulable. –

+0

@Eric - explicación perfecta, tiene sentido total. Gracias. –

5

Esto no tiene nada que ver con serialización o COM, por lo que vale la pena eliminar eso de la ecuación. Por ejemplo, aquí hay un programa corto pero completo que demuestra el problema:

using System; 

public struct Foo 
{ 
    // These change the calling code's correctness 
    public static bool operator ==(Foo f1, Foo f2) { return false; } 
    public static bool operator !=(Foo f1, Foo f2) { return false; } 

    // These aren't relevant, but the compiler will issue an 
    // unrelated warning if they're missing 
    public override bool Equals(object x) { return false; } 
    public override int GetHashCode() { return 0; } 
} 

public class Test 
{ 
    static void Main() 
    { 
     Foo f = new Foo(); 
     Console.WriteLine(f == null); 
    } 
} 

Creo que esto compila porque hay una conversión implícita de la nula literal a Nullable<Foo> y puede hacer esto legalmente:

Foo f = new Foo(); 
Foo? g = null; 
Console.WriteLine(f == g); 

Es interesante que esto solo suceda cuando == está sobrecargado: Marc Gravell lo ha detectado antes. No sé si es en realidad un error del compilador, o simplemente algo muy sutil en la forma en que se resuelven las conversiones, sobrecargas, etc.

En algunos casos (por ejemplo int, decimal) el compilador le advertirá sobre la conversión implícita - pero en otros (por ejemplo Guid) no lo hace.

+0

@Jon, no tuve que anular Equals o GetHashCode para simular este comportamiento tampoco. –

+1

@Stan: No, solo quería compilarlo sin advertencias. Estaba a punto de editar exactamente eso en la respuesta :) –