Pregunta

Actualizar

Ver mi disección de una parte de la especificación de C # a continuación; Creo que debe estar pasando algo, debido a parece que el comportamiento que estoy describiendo en esta pregunta en realidad viola la especificación.

Actualización 2!

OK, después de reflexionar, y en base a algunos comentarios, creo que ahora entiendo lo que está pasando. Las palabras "tipo de fuente" en la especificación se refieren al ser tipo convierte de - es decir, Type2 en mi ejemplo abajo - que simplemente significa que el compilador es capaz de reducir los candidatos a los dos operadores definido (desde Type2 es el tipo de fuente para ambos). Sin embargo, no se puede reducir las opciones más. Así que las palabras clave en la especificación (que se aplica a esta pregunta) son "Tipo de fuente" , que previamente malinterpretado (creo) en el sentido de "declarar tipo."


Original pregunta

Decir que tengo estos tipos definidos:

class Type0
{
    public string Value { get; private set; }

    public Type0(string value)
    {
        Value = value;
    }
}

class Type1 : Type0
{
    public Type1(string value) : base(value) { }

    public static implicit operator Type1(Type2 other)
    {
        return new Type1("Converted using Type1's operator.");
    }
}

class Type2 : Type0
{
    public Type2(string value) : base(value) { }

    public static implicit operator Type1(Type2 other)
    {
        return new Type1("Converted using Type2's operator.");
    }
}

A continuación, digo que hago esto:

Type2 t2 = new Type2("B");
Type1 t1 = t2;

Obviamente esto es ambigua, ya que no está claro qué operador implicit debe ser usado. Mi pregunta es - ya que no puedo ver cualquier forma de resolver esta ambigüedad (no es que puedo realizar alguna conversión explícita para aclarar cuál es la versión que quiero), y sin embargo, las definiciones de clase por encima de hacer la compilación -? ¿por qué el compilador de permitir que los operadores implicit juego en absoluto


Disección

OK, voy a paso a través del extracto de la C # spec citado por Hans Passant en un intento de dar sentido a esto.

  

Para el conjunto de tipos, D, a partir del cual   operadores de conversión definidas por el usuario se   ser considerado. Este conjunto consta de S   (Si S es una clase o estructura), la base   clases de S (si S es una clase), y T   (Si T es una clase o estructura).

Estamos convertir de Type2 ( S ) a Type1 ( T ). Así que parece que aquí D incluiría los tres tipos en el ejemplo: Type0 (porque es una clase base de S ), Type1 ( T ) y Type2 ( S ).

  

Para el conjunto de su caso   operadores de conversión definidas por el usuario, U.   Este conjunto consiste en la facilidad de uso definido   operadores de conversión implícitas declaradas   por las clases o estructuras en que D   convertir de un tipo que abarca S a   un tipo abarcado por T. Si U es   vacía, la conversión es indefinido y   se produce un error de tiempo de compilación.

Muy bien, tenemos dos operadores que cumplan estas condiciones. La versión declaró en Type1 cumple con los requisitos porque Type1 es en D y se convierte de Type2 (que obviamente engloba S ) para Type1 (que obviamente está abarcado por T ). La versión en Type2 también cumple con los requisitos para las mismas razones. Así T incluye ambos de estos operadores.

Por último, con respecto a la búsqueda de la "Tipo de fuente" más específica SX de los operadores en T

  

Si cualquiera de los operadores en U convertido del S, entonces SX es S.

Ahora, ambos operadores en T convertido del S - así que esto me dice que SX es < strong> S .

¿No significa esto que la versión Type2 se debe utilizar?

Pero espera! Estoy confundido!

¿No podría yo he Sólo versión de Type1 definida del operador, en cuyo caso, el único candidato restante sería la versión de Type1, y sin embargo, de acuerdo con el SPec SX sería Type2? Esto parece como un posible escenario en el que la especificación algo mandatos imposible (a saber, que la conversión declarada en Type2 se debe utilizar cuando en realidad no existe).

¿Fue útil?

Solución

realmente no quiere que sea un error en tiempo de compilación sólo para definir las conversiones, que podría causa ambigüedad. Supongamos que alteramos Type0 para almacenar un doble, y por alguna razón queremos proporcionar conversiones separadas a entero con signo y entero sin signo.

class Type0
{
    public double Value { get; private set; }

    public Type0(double value)
    {
        Value = value;
    }

    public static implicit operator Int32(Type0 other)
    {
        return (Int32)other.Value;
    }

    public static implicit operator UInt32(Type0 other)
    {
        return (UInt32)Math.Abs(other.Value);
    }

}

Esta compila bien, y yo uso puede utilizar tanto con conversiones

Type0 t = new Type0(0.9);
int i = t;
UInt32 u = t;

Sin embargo, es un error de compilación para tratar float f = t porque cualquiera de las conversiones implícitas podrían utilizarse para llegar a un tipo entero que luego se puede convertir en flotador.

Sólo queremos que el compilador se quejan de estas ambigüedades más complejas cuando se utilizan realmente, ya que nos gustaría el Type0 posterior para compilar. Para mantener la coherencia, la ambigüedad más simple también debe causar un error en el punto que lo utilice en lugar de cuando se define.

Editar

Desde que Hans se quitó la respuesta que citó la especificación, aquí hay un rápido repaso por la parte de la C # spec que determina si una conversión es ambigua, habiendo definido U para ser el conjunto de todas las conversiones que posiblemente podría hacer el trabajo:

  
      
  • Para los más tipo de fuente específica, SX, de los operadores en U:      
        
    • Si cualquiera de los operadores en U convertido del S, entonces SX es S.
    •   
    • Si no, SX es el tipo más abarcado en el conjunto combinado de tipos de destino de los operadores en U. Si no tipo más abarcado se puede encontrar, a continuación, la conversión es ambiguo y se produce un error de tiempo de compilación.
    •   
  •   

Parafraseado, preferimos una conversión que convierte directamente de S, de lo contrario se prefiere que el tipo que es "más fácil" para convertir S a. En ambos ejemplos, tenemos dos conversiones de S disponibles. Si no hubo conversiones de Type2, que prefiere una conversión de más de un Type0 de object. Si hay un solo tipo es, obviamente, la mejor opción para convertir, no somos capaces aquí.

  
      
  • Para la mayoría de los tipos de destino específico, TX, de los operadores en U:      
        
    • Si cualquiera de los operadores en U convertir a T, a continuación, TX es T.
    •   
    • Si no, TX es el tipo más abarca en el conjunto combinado de tipos de destino de los operadores en U. Si no la mayoría del tipo que abarca se pueden encontrar, a continuación, la conversión es ambiguo y se produce un error de tiempo de compilación.
    •   
  •   

Una vez más, hubiéramos preferido para convertir directamente a T, pero se conformará con el tipo que es "más fácil" para convertir al ejemplo de T. En Dan, tenemos dos conversiones a T disponibles. En mi ejemplo, los objetivos son posibles Int32 y UInt32, y tampoco es un partido mejor que el otro, por lo que aquí es donde falla la conversión. El compilador no tiene forma de saber si los medios float f = t float f = (float)(Int32)t o float f = (float)(UInt32)t.

  
      
  • Si T contiene exactamente un operador de conversión definida por el usuario que convierte de SX a TX, entonces este es el operador más específico de conversión. Si no existe tal operador, o si existe más de uno de tales operador, a continuación, la conversión es ambiguo y se produce un error de tiempo de compilación.
  •   

En el ejemplo de Dan, no somos capaces aquí porque tenemos dos conversiones que quedan de SX a TX. Podríamos tener ninguna conversión de SX a TX si elegimos diferentes conversiones al decidir SX y TX. Por ejemplo, si tuviéramos un Type1a derivado de Type1, entonces podríamos tener conversiones de Type2 a Type1a y de Type0 a Type1 Estos seguiría siendo darnos SX = Tipo 2 y TX = Tipo 1, pero que en realidad no tienen ningún tipo de conversión de Tipo 2 a Tipo 1. Esto está bien, porque esto realmente es ambigua. El compilador no sabe si se deben convertir a Type2 Type1a y luego se echó a Tipo 1, o un yeso para Type0 primero para que se pueda usar que la conversión al Tipo 1.

Otros consejos

En última instancia, no puede ser legislado con total éxito. Usted y yo podría publicar dos asambleas. Ellos nos podrían empezar a utilizar ensambla el uno al otro, mientras que la actualización de la nuestra. Entonces podríamos cada proporcionar conversiones implícitas entre los tipos definidos en cada montaje. Sólo cuando nos liberamos la próxima versión, este podría ser capturado, en lugar de en tiempo de compilación.

Hay una ventaja en no intentar prohibir cosas que no pueden ser prohibidos, ya que hace que para mayor claridad y consistencia (y hay una lección para los legisladores en eso).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top