c# - multiple - ¿Por qué hay una restricción para la conversión explícita de un genérico a un tipo de clase pero no hay ninguna restricción para convertir un genérico a un tipo de interfaz?
reflection c# (4)
Creo que la diferencia entre el envío a una interfaz y el envío a una clase radica en el hecho de que c # admite la "herencia" múltiple solo para las interfaces. Qué significa eso? El compilador solo puede determinar en tiempo de compilación si una conversión es válida o no para una clase porque C # no permite la herencia múltiple para las clases.
Por otro lado, el compilador no sabe en tiempo de compilación si su clase implementa o no la interfaz utilizada en la conversión. ¿Por qué? Alguien podría heredar de su clase e implementar la interfaz utilizada en su lanzamiento. Por lo tanto, el compilador no es consciente de esto en el momento de la compilación. (Ver SomeMethod4()
abajo).
Sin embargo, el compilador puede determinar si una conversión a una interfaz es válida o no, si su clase está sellada.
Considere el siguiente ejemplo:
interface ISomeInterface
{}
class SomeClass
{}
sealed class SealedClass
{
}
class OtherClass
{
}
class DerivedClass : SomeClass, ISomeInterface
{
}
class MyClass
{
void OtherMethod(SomeClass s)
{
ISomeInterface t = (ISomeInterface)s; // Compiles!
}
void OtherMethod2(SealedClass sc)
{
ISomeInterface t = (ISomeInterface)sc; // Does not compile!
}
void OtherMethod3(SomeClass c)
{
OtherClass oc = (OtherClass)c; // Does not compile because compiler knows
} // that SomeClass does not inherit from OtherClass!
void OtherMethod4()
{
OtherMethod(new DerivedClass()); // In this case the cast to ISomeInterface inside
} // the OtherMethod is valid!
}
Lo mismo es cierto para los genéricos.
Espero que esto ayude.
Mientras leía la documentación de Microsoft, me topé con un ejemplo de código tan interesante:
interface ISomeInterface
{...}
class SomeClass
{...}
class MyClass<T>
{
void SomeMethod(T t)
{
ISomeInterface obj1 = (ISomeInterface)t;//Compiles
SomeClass obj2 = (SomeClass)t; //Does not compile
}
}
Esto significa que puede convertir su genérico a la interfaz explícitamente pero no a la clase a menos que tenga una restricción. Bueno, todavía no puedo entender la lógica detrás de la decisión, ya que tanto los tipos de interfaz como los de clase están lanzando excepciones, así que, ¿por qué protegerse solo contra una de estas excepciones?
Por cierto, hay una forma de evitar el error de compilación, pero esto no elimina el problema lógico en mi cabeza:
class MyOtherClass
{...}
class MyClass<T>
{
void SomeMethod(T t)
{
object temp = t;
MyOtherClass obj = (MyOtherClass)temp;
}
}
Eso es exactamente lo que obtienes en circunstancias normales, sin genéricos, cuando intentas elegir entre clases sin relación de herencia:
public interface IA
{
}
public class B
{
}
public class C
{
}
public void SomeMethod( B b )
{
IA o1 = (IA) b; <-- will compile
C o2 = (C)b; <-- won''t compile
}
Por lo tanto, sin una restricción, la clase genérica se comportará como si no hubiera una relación entre las clases.
Continuado...
Bueno, digamos que alguien hace esto:
public class D : B, IA
{
}
Y luego llama:
SomeMethod( new D() );
Ahora verás por qué el compilador deja pasar la interfaz de conversión. Realmente no se puede saber en tiempo de compilación si una interfaz está implementada o no.
Recuerde que la clase D puede estar muy bien escrita por alguien que esté utilizando su ensamblaje, años después de haberlo compilado. Así que no hay posibilidad de que el compilador pueda negarse a compilarlo. Debe comprobarse en tiempo de ejecución.
La gran diferencia es que se garantiza que una interfaz es un tipo de referencia. Los tipos de valor son los creadores de problemas. Se menciona explícitamente en la Especificación de lenguaje C #, capítulo 6.2.6, con un excelente ejemplo que demuestra el problema:
Las reglas anteriores no permiten una conversión explícita directa de un parámetro de tipo sin restricciones a un tipo sin interfaz, lo que puede ser sorprendente. El motivo de esta regla es evitar la confusión y aclarar la semántica de tales conversiones. Por ejemplo, considere la siguiente declaración:
class X<T>
{
public static long F(T t) {
return (long)t; // Error
}
}
Si se permitiera la conversión explícita directa de t a int, se podría esperar fácilmente que XF (7) devolvería 7L. Sin embargo, no lo haría, porque las conversiones numéricas estándar solo se consideran cuando se sabe que los tipos son numéricos en el momento de la compilación. Para que la semántica quede clara, el ejemplo anterior debe escribirse:
class X<T>
{
public static long F(T t) {
return (long)(object)t; // Ok, but will only work when T is long
}
}
Este código ahora se compilará, pero la ejecución de XF (7) generará una excepción en el tiempo de ejecución, ya que un int en caja no se puede convertir directamente a un largo.
No hay nada malo en ello. La única diferencia es que, en el primer caso, el compilador puede detectar en el momento de la compilación que no hay un lanzamiento posible, pero no puede estar tan "seguro" acerca de las interfaces, por lo que el error, en este caso, aumentará solo en el tiempo de ejecución. Asi que,
// Compiles
ISomeInterface obj1 = (ISomeInterface)t;
// Сompiles too!
SomeClass obj2 = (SomeClass)(object)t;
producirá los mismos errores en tiempo de ejecución.
Por lo tanto, la razón puede ser: el compilador no sabe qué interfaces implementa la clase, pero sí conoce la herencia de clases (por lo tanto, (SomeClass)(object)t
funciona). En otras palabras: la conversión no válida está prohibida en CLR, la única diferencia es que en algunos casos puede detectarse en el momento de la compilación, y en algunos no. La razón principal detrás de eso, incluso si el compilador conoce todas las interfaces de clase, no sabe acerca de sus descendientes, que pueden implementarlo, y son válidas para ser T
Considere el siguiente escenario:
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
MyClass<SomeClass> mc = new MyClass<SomeClass>();
mc.SomeMethod(new SomeClassNested());
}
}
public interface ISomeInterface
{
}
public class SomeClass
{
}
public class SomeClassNested : SomeClass, ISomeInterface
{
}
public class MyClass<T>
{
public void SomeMethod(T t)
{
// Compiles, no errors at runtime
ISomeInterface obj1 = (ISomeInterface)t;
}
}
}