2012-01-18 4 views
19

Tengo una situación en la que me gustaría explicar el comportamiento del compilador. Teniendo en cuenta un poco de código:La palabra clave sellada afecta la opinión del compilador sobre un molde

interface IFoo<T> 
{ 
    T Get(); 
} 

class FooGetter : IFoo<int> 
{ 
    public int Get() 
    { 
     return 42; 
    } 
} 

Los siguientes compila y ejecuta:

static class FooGetterGetter 
{ 
    public static IFoo<T> Get<T>() 
    { 
     return (IFoo<T>)new FooGetter(); 
    } 
} 

Si hacemos un cambio a la firma de la clase Foo y añadir la sealed palabra clave:

sealed class FooGetter : IFoo<int> // etc 

Entonces obtengo un error de compilación en la siguiente línea:

return (IFoo<T>)new FooGetter(); 

de:

No se puede convertir tipo 'MyNamespace.FooGetter' a 'MyNamespace.IFoo <T>'

Puede alguien explicar lo que está sucediendo aquí con respecto a la palabra clave sealed? Esto es C# 4 en contra de un proyecto .NET 4 en Visual Studio 2010.

Actualización: curiosamente me encontré con esa parte del comportamiento cuando me preguntaba por qué el código siguiente corrige cuando se aplica sealed:

return (IFoo<T>)(IFoo<int>)new FooGetter(); 

actualización: sólo para aclarar, todo funciona muy bien cuando el tipo de T solicitado es el mismo que el tipo de T utilizado por el tipo de hormigón. Si los tipos son diferentes, la conversión falla en tiempo de ejecución con algo como:

No se puede convertir objeto de tipo 'MyNamespace.StringFoo' para escribir 'MyNamespace.IFoo`1 [System.Int32]'

En el ejemplo anterior, StringFoo : IFoo<string> y la persona que llama solicita obtener un int.

+2

no tengo la respuesta, pero me imagino que tiene algo que ver con el hecho de que '' IFoo es un tipo genérico abierto mientras que 'implementos FooGetter' 'IFoo ' que es un tipo genérico cerrado. –

+1

Solo una nota: me aseguré de tener el comportamiento definido antes de publicar la pregunta, no quería hacer el ridículo :-) Puedo ver por qué se puede permitir, el compilador no puede garantizar lo que está pasando, solo sabe que tiene posibilidades de éxito. Pero por alguna razón, elimina esa misma posibilidad de éxito cuando la palabra clave sellada está presente debido a que supone que, como no se puede derivar, no puede coincidir con T. –

+0

+1 Interesante :) – leppie

Respuesta

8

Porque FooGetter es una implementación explícita de IFoo<int> en lugar de implementar IFoo<T> genéricamente. Como está sellado, el compilador sabe que no hay manera de convertirlo a un IFoo<T> genérico si T es algo distinto de int. Si no estuviera sellado, el compilador le permitiría compilar y lanzar una excepción en tiempo de ejecución si T no era un int.

Si intenta usarlo con otra cosa que no sea un int (por ejemplo FooGetterGetter.Get<double>();) se obtiene una excepción:

No se puede convertir objeto de tipo 'MyNamespace.FooGetter' al tipo 'MyNamespace.IFoo`1 [System.Double] '.

Lo que no estoy seguro de qué es el compilador hace no generará un error de la versión no sellada.¿Cómo podría su subclase FooGetter que new FooGetter() le brinde algo que implemente IFoo<{something_other_than_int}>?

Actualización:

por Dan Bryant y Andras Zoltan existen métodos para devolver una clase derivada de un constructor (o, posiblemente, más precisamente por el compilador volver a un tipo diferente mediante el análisis de atributos). Entonces, técnicamente esto es factible si la clase no está sellada.

+4

+1 pero para responder a su última pregunta, bueno, es una interfaz, ¿así que puede agregar otra instancia de la interfaz? El compilador no ve el 'nuevo' solo ve una expresión de tipo' FooGetter' que, cuando no está 'sellado' podría ser cualquier tipo derivado de él. –

+0

@DStanley Veo tu punto. Su explicación también se aplica a la conversión dos veces para evitar la palabra clave sellada. En la situación de lanzar dos veces, sabes que puedes convertir a 'IFoo ' si el tipo concreto usa 'int', entonces puedes convertir' IFoo 'a' IFoo ' por la misma razón que el compilador te permite hacer 'FooGetter' a' IFoo '(¿no estaba sellado)? Por alguna razón, permite que se produzcan errores en el tiempo de ejecución. –

+3

Para completar el punto de Andras, podrías tener 'SecondFoo: FooGetter, IFoo '. Curioso en cuanto a por qué ignora el 'nuevo' en ese caso, que no garantiza ningún tipo derivado ... a menos que asuma que un tipo base podría implementar la interfaz correcta y no molesta en buscar, dejando errores en el tiempo de ejecución. –

4

Cuando una clase de sellar cualquier clase derivada podría implementar IFoo<T>:

class MyClass : FooGetter, IFoo<double> { } 

Cuando FooGetter se marca como sellado, el compilador sabe que no puede ser posible para cualquier implementaciones adicionales de IFoo<T> distintos IFoo<int> pudieran existir para FooGetter.

Esto es un buen comportamiento, le permite detectar problemas con su código en tiempo de compilación en lugar de en tiempo de ejecución.

La razón por la cual (IFoo<T>)(IFoo<int>)new FooGetter(); funciona es porque ahora está representando su clase sellada como IFoo<int> que podría ser implementada por cualquier cosa. También es un buen trabajo, ya que no es accidental, sino que descarta a propósito la comprobación del compilador.

1

Solo para agregar a las respuestas existentes: Esto realmente no tiene nada que ver con los genéricos utilizados.

Considere este ejemplo sencillo:

interface ISomething 
{ 
} 

class OtherThing 
{ 
} 

Luego diciendo (dentro de un método):

OtherThing ot = XXX; 
ISomething st = (ISomething)ot; 

funciona bien. El compilador no sabe si un OtherThing podría ser un ISomething, por lo que nos cree cuando decimos que tendrá éxito. Sin embargo, si cambiamos OtherThing a tipo sellado (es decir, sealed class OtherThing { } o struct OtherThing { }), entonces el molde ya no está permitido. El compilador sabe que no puede ir bien (excepto si ot fuera null, pero las reglas de C# aún no permiten el reparto de un tipo sellado a una interfaz no implementada por ese tipo sellado).

En cuanto a la actualización de la pregunta: Escribir (IFoo<T>)(IFoo<int>)new FooGetter() no es muy diferente de escribir (IFoo<T>)(object)new FooGetter(). Puedes "hacer permitido" cualquier lanzamiento (con genéricos o sin) pasando por algún tipo intermedio que sea ciertamente/posiblemente un antepasado de los dos tipos que quieres convertir. Es muy similar a este patrón:

void MyMethod<T>(T t) // no "where" constraints on T 
{ 
    if (typeof(T) = typeof(GreatType)) 
    { 
    var tConverted = (GreatType)(object)t; 
    // ... use tConverted here 
    } 
    // ... other stuff 
} 
Cuestiones relacionadas