2012-01-20 21 views
14

simplificado de this question y se deshizo de posible afectar a LINQPad (sin offsensive), una aplicación de consola simple como esto:grupo método implícito Gotcha conversión (parte 2)

public class Program 
{ 
    static void M() { }  
    static void Main(string[] args) 
    { 
     Action a = new Action(M); 
     Delegate b = new Action(M); 
     Console.WriteLine(a == b);  //got False here 
     Console.Read(); 
    }   
} 

Los resultados "falsos" de la operadora ceq en CIL del código anterior (visite la pregunta original para más detalles). Entonces mis preguntas son:

(1) ¿Por qué == se está traduciendo como ceq en lugar de call Delegate Equals?

Aquí no me importa la (des) envoltura entre delegado y acción. Por último, al evaluar a == b, a es de tipo Action mientras que b es Delegate. De la especificación:

7.3.4 resolución sobrecarga operador binario

una operación del op y forma x, donde op es un operador binario sobrecargable, x es una expresión de tipo X, e Y es una expresión de tipo Y, se procesa de la siguiente manera:

• Se determina el conjunto de operadores definidos por el usuario posibles proporcionados por X e Y para el operador operativo op (x, y). El conjunto consiste en la unión de los operadores candidatos proporcionados por X y los operadores candidatos proporcionados por Y, cada uno determinado utilizando las reglas del §7.3.5. Si X e Y son del mismo tipo, o si X e Y se derivan de un tipo de base común , los operadores candidatos compartidos solo se producen en el conjunto establecido una vez.

• Si el conjunto de candidatos de operadores definidos por el usuario no está vacío, se convierte en el conjunto de operadores candidatos para la operación . De lo contrario, las implementaciones del operador binario predefinido , incluidos sus formularios levantados, se convierten en el conjunto de operadores candidatos para la operación. Las implementaciones predefinidas de un operador dado se especifican en la descripción del operador (§7.8 a §7.12).

• Las reglas de resolución de sobrecarga de §7.5.3 se aplica al conjunto de los operadores de candidatos para seleccionar el mejor operador con respecto a la lista de argumentos (x, y), y esto se convierte en operador el resultado de la proceso de resolución de sobrecarga. Si la resolución de sobrecarga no selecciona un único mejor operador, se produce un error de tiempo de enlace.

operadores definidos por el usuario

7.3.5 candidatos

Dado un tipo T y un operador op operación (A), donde op es un operador sobrecargable y A es una lista de argumentos, el conjunto de los operadores definidos por el usuario candidatos provisto por T para el operador op (A) se determina de la siguiente manera:

• Determine el tipo T0. Si T es un tipo anulable, T0 es su tipo subyacente, de lo contrario T0 es igual a T.

• Para todas las declaraciones op operador en T0 y todas levantadas formas de dichos operadores, si al menos un operador es aplicable (§7.5.3.1) con respecto a la lista de argumentos A, entonces el conjunto de operadores candidatos consta de todos los operadores aplicables en T0.

• De lo contrario, si T0 es un objeto, el conjunto de operadores candidatos está vacío.

• De lo contrario, el conjunto de los operadores candidatos proporcionados por T0 es el conjunto de los operadores candidatos proporcionados por la clase base directa de T0, o la clase base efectiva de T0 T0 si es un parámetro de tipo.

De la especificación, A y B tienen una misma clase base Delegate, obviamente, la regla operador == definido en Delegate debe aplicarse aquí (el operador == invoca esencialmente Delegate.Equals). Pero ahora parece que la lista de candidatos de los operadores definidos por el usuario está vacía y, por último, se aplica Object ==.

(2) ¿Debería (Does) el código FCL obedecer la especificación del lenguaje C#? Si no, mi primera pregunta no tiene sentido porque algo es tratado especialmente. Y luego podemos responder todas estas preguntas usando "oh, es un tratamiento especial en FCL, pueden hacer algo que nosotros no podemos. La especificación es para programadores externos, no seas tonto".

+0

Es por eso que es mejor usar 'Equals' cuando se espera la semántica del tipo de valor. Debido a (potencialmente) sobrecargas de operador rotas. – Groo

+0

@Groo: Exacto. Y, por cierto, recibí una advertencia de compilación para el código en la pregunta 'Posible comparación de referencia involuntaria; para obtener una comparación de valores, echa el lado derecho para escribir 'System.Action'. –

+0

Definitivamente tiene que ver con el trato especial de los delegados en general, ya que intentar lo mismo con una jerarquía de clases definida por el usuario llama al método '==' personalizado – AakashM

Respuesta

4

Existen dos tipos de operadores: operadores definidos por el usuario y operadores predefinidos. La Sección 7.3.5 "Operadores definidos por el usuario candidato" no se aplica a los operadores predefinidos. Por ejemplo, los operadores en decimal se ven como operadores definidos por el usuario en un descompilador, pero C# los trata como operadores predefinidos y les aplica promociones numéricas (la promoción numérica no se aplica a los operadores definidos por el usuario).

La sección 7.10.8 "Delegar operadores de igualdad" define operator ==(Delegate, Delegate) como un operador predefinido, por lo que creo que todas las reglas sobre operadores definidos por el usuario no se aplican a este operador (aunque esto no es 100% claro en la especificación como en este caso, el operador predefinido no se aplica siempre que lo haga el operador definido por el usuario).

Every delegate type implicitly provides the following predefined comparison operators: 
bool operator ==(System.Delegate x, System.Delegate y); 
bool operator !=(System.Delegate x, System.Delegate y); 

Pero System.Delegate sí no se considera un tipo de delegado, por lo que el único candidato para la resolución de sobrecarga es operator ==(object, object).

+1

Entonces, ¿por qué la salida cambia a 'True' si cambia a' Console.WriteLine ((Delegado) a == b); '? Ahora el tipo de tiempo de compilación de ninguno de los operandos es un "tipo de delegado" (ya que como dices la clase no concreta 'System.Delegate' no es un" tipo de delegado "), pero aún se usa la sobrecarga de tipo delegado. (Acabo de encontrar este hilo porque mi propia pregunta [Resolución de sobrecarga en el operador == con tipos de delegados genéricos variantes] (http://stackoverflow.com/questions/22408165/) se ha vinculado a esta.) –

-1

La clave aquí es que el operador == y el método Equals para el tipo Delegate son dos cosas diferentes. Para los tipos de referencia, el == mira para ver si ambas referencias apuntan al mismo objeto a menos que el operador == esté anulado (vea: == Operator (C#)).

Puesto que usted está creando dos Action objetos diferentes, a pesar de que internamente invocan el mismo método, que son diferentes objetos en diferentes ubicaciones en la memoria, y no son de un valor o string tipo, por lo == es en este caso un ReferenceEquals y no invoca el método Delegate.Equals, que se ha reemplazado para ver si los dos objetos hacen lo mismo. Para tipos de referencia que no sean string, este es el comportamiento predeterminado de == o Equals.

+1

Para tipos de referencia, el operador '==' hace lo que está definido para hacer por ese tipo en particular.Muchos tipos no realizan una verificación de igualdad de referencia. –

+0

Agregué la cláusula 'a menos que el operador "==" se haya anulado' a mi declaración. La clave es que la opción "==" op generalmente se utiliza para comparar la referencia, y el método Equals generalmente se usa para comparar el valor. – SamuelWarren

+1

@highphilospher: Estoy deshaciendo el downvote, pero C# no comparte la filosofía de Java de '==' que se usa para la igualdad de referencia. Mayormente no lo es. Si quiere igualdad de referencia, use el método estático 'object.ReferenceEquals'. –

5

El compilador funciona de forma muy diferente e inusual con los delegados. Hay una gran cantidad de manejo implícito. Observe que la regla de 'tipo de base común' en esta guía se aplica a 'operadores definidos por el usuario'. Los delegados son internos y del sistema. Por ejemplo, puede escribir Action a = M; en lugar de Action a = new Action(M);. Y puede agregar a += M; después de eso. Compruebe lo que sucede en CIL, es interesante por primera vez.

Además, es peligroso y no es trivial comparar delegados. Cada delegado es realmente un delegado de multidifusión. Puede agregar varios punteros a la función al mismo delegado. ¿Los delegados [L(); M(); N();] equivalen a delegar [M();]? El puntero de función contiene una instancia de clase (por ejemplo, un método). ¿[a.M();] es igual a [b.M();]? Todo eso depende de un caso, y la implementación de la comparación requiere pasar por la lista de invocación.

Delegación heredada del tipo de base común Delegado es implícito y este problema se puede enfrentar en otros escenarios, p. restricción genérica: no puede especificar Delegate como una restricción para el parámetro genérico T. Aquí el compilador lo rechaza explícitamente. Lo mismo acerca de crear sus propias clases, heredadas de Delegate.

Responde a las dos preguntas: "Delegar" no es solo FCL, sino que está estrechamente relacionado con el compilador.Si realmente desea el comportamiento del comparador delegado de Microsoft, simplemente llame explícitamente Equals(a, b)

+0

Esta fue una pregunta sobre la especificación del lenguaje, y no creo que "Esto es magia del compilador" sea una respuesta suficiente para ese tipo de preguntas. – Daniel

+0

Y sí, la comparación de delegados de multidifusión no es trivial, pero la especificación de C# define claramente cómo funciona. – Daniel

3

advertencia CS0253: Posible comparación no intencional de la referencia; para obtener una comparación de valores, coloque el lado derecho para escribir 'System.Action'

Esa es la advertencia que recibe para ese código C#. No ignore esa advertencia, el equipo de C# era consciente de que el código que generaron para esta comparación fue inesperado. No hicieron tienen para generar ese código, fácilmente podrían haber hecho lo que esperabas. Al igual que este código hace:

Module Module1 
    Sub M() 
    End Sub 

    Sub Main() 
     Dim a = New Action(AddressOf M) 
     Dim b = DirectCast(New Action(AddressOf M), [Delegate]) 
     Console.WriteLine(a = b)  ''got True here 
     Console.Read() 
    End Sub 
End Module 

que genera casi el mismo MSIL, excepto que en lugar de ceq que se obtiene:

IL_001d: call bool [mscorlib]System.Delegate::op_Equality(class [mscorlib]System.Delegate, 
                  class [mscorlib]System.Delegate) 

¿Qué lo que esperaba el código C# haría. Ese era el código de VB.NET, en caso de que no lo reconocieras. De lo contrario, la razón por la que Microsoft mantiene dos principales idiomas administrados, aunque tengan capacidades muy similares. Pero con opciones de usabilidad muy diferentes. Cada vez que había más de una forma de generar código, el equipo de C# elegía consistentemente para rendimiento, el equipo de VB.NET consistentemente para conveniencia.

Y el rendimiento sin duda es la clave aquí, la comparación de los objetos de delegado es caro. Las reglas se detallan en Ecma-335, sección II.14.6.1. Pero puedes razonar por ti mismo, hay muchas cosas que hacer. Necesita comprobar si el objeto de destino delegado es compatible. Y para cada argumento, debe verificar si el valor es convertible. Gasto que el equipo de C# no quiere ocultar.

Y no, recibe la advertencia para recordarle que tomaron la decisión no intuitiva. .

+0

Lo más sorprendente en C# es que tanto '' (Acción) a == (Acción) b' como '(Delegado) a == (Delegado) b' va a delegar la comparación de valores, el caso' (Acción) a == (Delegado) b 'va a la verificación de igualdad de referencia (con advertencia de tiempo de compilación). Esto no es natural, en particular porque 'Action' es implícitamente convertible (por conversión de referencia) en' Delegate'. –

+0

Tienes la mitad de la recompensa por tu respuesta (lo cual es bueno). Pero todavía siento que el compilador C# hace algo que no se ve fácilmente de acuerdo con la Especificación del lenguaje C#, y quería una respuesta al respecto. También vea el hilo enlazado _Overload resolution on operator '==' con variante genérico delegate types_. Pero estoy de acuerdo en que comparar los delegados de multidifusión es costoso, y verificar la igualdad de referencia es muy barato, y que podría haber diferentes filosofías en C# y VB.NET sobre qué "optimizar". –