2009-07-30 23 views
32

Dada la siguiente estructura:Error del compilador C#? ¿Por qué no se compila esta conversión implícita definida por el usuario?

public struct Foo<T> 
{ 
    public Foo(T obj) { } 

    public static implicit operator Foo<T>(T input) 
    { 
     return new Foo<T>(input); 
    } 
} 

Este código se compila:

private Foo<ICloneable> MakeFoo() 
{ 
    string c = "hello"; 
    return c; // Success: string is ICloneable, ICloneable implicitly converted to Foo<ICloneable> 
} 

Pero este código no compila - ¿por qué?

private Foo<ICloneable> MakeFoo() 
{ 
    ICloneable c = "hello"; 
    return c; // Error: ICloneable can't be converted to Foo<ICloneable>. WTH? 
} 

Respuesta

30

Al parecer, las conversiones definidas por el usuario implícita no funcionan cuando uno de los tipos es una interfaz. A partir de las especificaciones: C#


6.4.1 conversiones definidas por el usuario permitidas

C# sólo permite ciertas conversiones definidas por el usuario para ser declarado. En particular, no es posible redefinir una conversión implícita o explícita ya existente. Para un tipo de fuente dado S y tipo de objetivo T, si S o T son tipos anulables, deje que S0 y T0 se refieran a sus tipos subyacentes, de lo contrario S0 y T0 son iguales a S y T respectivamente. se permite una clase o struct para declarar una conversión de un tipo de fuente S a un tipo de objetivo T sólo si todos de los siguientes son verdaderas:

  • S0 y T0 son diferentes tipos.
  • O bien S0 o bien T0 es la clase o tipo de estructura en el que tiene lugar la declaración del operador.
  • Ni S0 ni T0 es un tipo de interfaz.
  • Excluyendo conversiones definidas por el usuario, una conversión no existe de S a T o de T a S.

En su primer método, ambos tipos no se interconectan tipos, por lo que el usuario define implícita la conversión funciona

Las especificaciones no son muy claras, pero me parece que si uno de los tipos implicados es un tipo de interfaz, el compilador ni siquiera intenta buscar las conversiones implícitas definidas por el usuario.

+0

Wow. Qué extraño requisito. Me gustaría saber de Lippert, Skeet u otro experto en C# por qué los tipos de interfaz no funcionarán para esto; seguramente debe haber una buena razón detrás de esta rareza. –

+0

Después de experimentar un poco, veo que las interfaces parecen ser la clave aquí. Lo que es extraño es que parece menos un salto mental para el compilador averiguar qué hacer muchas veces. Hmm. –

+0

Estaría muy interesado en una explicación de cómo compila la primera muestra. Dadas las reglas en la sección 6.4.4, no veo cómo se elige la conversión 'Foo ' dado que 'ICloneable' no abarca' string' (ya que 'ICloneable' es una interfaz) y' Foo 'es no comprendido por 'Foo ' (ya que no hay una conversión implícita de 'Foo ' a 'Foo '). Quizás 6.1.9 "Conversiones implícitas que involucran parámetros de tipo" está entrando en juego de alguna manera? – zinglon

24

(seguimiento a las observaciones de la respuesta aceptada.)

Sí, esto es una parte muy, muy confuso de la especificación. Todo el tema sobre "tipos abarcadoras" en particular es profundamente defectuoso. He intentado durante varios años encontrar el tiempo para reescribir completamente toda esa sección en algo más coherente, pero nunca ha sido una prioridad lo suficientemente alta.

Esencialmente, lo que tenemos aquí es una contradicción; nosotros decir que no hay conversiones implícitas definidas por el usuario que involucren interfaces, pero claramente eso no es cierto en este caso; hay una conversión implícita definida por el usuario de IC a Foo<IC>, demostrado por el hecho de que una cadena va al Foo<IC> a través de esa conversión.

Lo que realmente debería ser mejor haciendo hincapié en esta línea es que usted ha citado:

En particular, no es posible redefinir un explícita o implícita de conversión ya existente.

Eso es lo que motiva todo esto; el deseo de no permitirle pensar alguna vez que está realizando una prueba de tipo preservación de la representación cuando de hecho está llamando a un método definido por el usuario. Considere, por ejemplo, esta variación:

interface IBar {} 
interface IFoo : IBar {} 
class Foo<T> : IFoo 
{ 
    public static explicit operator Foo<T>(T input) { whatever } 
} 
class Blah : Foo<IBar> {} 
... 
IBar bar = new Blah(); 
Foo<IBar> foo = (Foo<IBar>)bar; 

Ahora, ¿eso llaman la conversión explícita definida por el usuario o no? El objeto realmente se deriva de Foo, por lo que esperaría que no lo haga; esto debería ser una prueba de tipo simple y una asignación de referencia, no una llamada a un método de ayuda. Un molde en un valor de interfaz siempre se trata como una prueba de tipo porque casi siempre es posible que el objeto realmente sea de ese tipo y realmente implementa esa interfaz. No queremos negarle la posibilidad de realizar una conversión de preservación de representación barata.

+0

Ok, las reglas especiales sobre el envío de interfaces y el motivo de estas reglas me ayudan a comprender esto. Gracias por aclarar, Eric. –

+0

Debo añadir, mi ejemplo de código "se siente" como debería funcionar. Algo así como la covarianza, es una de esas cosas que tu mente dice que debería funcionar, pero no es así. Je. –

+0

¿Esto significa que en el ejemplo anterior (Foo ) la barra se trata como un "elenco" en lugar de una conversión explícita? –

Cuestiones relacionadas