[英]Why there is a restriction for explicit casting a generic to a class type but there is no restriction for casting a generic to an interface type?
在阅读Microsoft文档时,我偶然发现了这样一个有趣的代码示例:
interface ISomeInterface
{...}
class SomeClass
{...}
class MyClass<T>
{
void SomeMethod(T t)
{
ISomeInterface obj1 = (ISomeInterface)t;//Compiles
SomeClass obj2 = (SomeClass)t; //Does not compile
}
}
这意味着除非您有约束,否则可以显式地将通用转换为接口而不是类。 好吧,我仍然无法理解决策背后的逻辑,因为接口和类类型转换都抛出异常,那么为什么只能防止这些异常中的一个呢?
BTW-有一种解决编译错误的方法,但这并没有消除我头脑中的逻辑混乱:
class MyOtherClass
{...}
class MyClass<T>
{
void SomeMethod(T t)
{
object temp = t;
MyOtherClass obj = (MyOtherClass)temp;
}
}
当你尝试在没有继承关系的类之间进行转换时,这正是你在正常情况下得到的 - 没有泛型 -
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
}
因此,没有约束,泛型类的行为就好像类之间没有关系一样。
继续...
好吧,让我们说有人这样做:
public class D : B, IA
{
}
然后打电话:
SomeMethod( new D() );
现在你将看到为什么编译器允许接口转换通过。 如果实现了接口,它实际上无法在编译时知道。
请记住,D类可能很好地由使用你的程序集的人编写 - 在编译之后的几年。 所以编译器不可能拒绝编译它。 必须在运行时检查它。
最大的区别是接口保证是参考类型。 价值类型是麻烦制造者。 它在C#语言规范第6.2.6章中有明确提及,其中有一个很好的例子来说明问题:
上述规则不允许从无约束类型参数直接显式转换为非接口类型,这可能是令人惊讶的。 此规则的原因是为了防止混淆并使这种转换的语义清晰。 例如,请考虑以下声明:
class X<T>
{
public static long F(T t) {
return (long)t; // Error
}
}
如果允许将t直接显式转换为int,则可能很容易预期XF(7)将返回7L。 但是,它不会,因为仅在编译时已知类型为数字时才考虑标准数字转换。 为了使语义清晰,必须编写上面的示例:
class X<T>
{
public static long F(T t) {
return (long)(object)t; // Ok, but will only work when T is long
}
}
此代码现在将编译,但执行XF(7)将在运行时抛出异常,因为boxed int无法直接转换为long。
它没有错。 唯一的区别是,在第一种情况下,编译器可以在编译时检测到哪里没有可能的转换,但他不能对接口如此“确定”,因此在这种情况下,错误仅在运行时才会上升。 所以,
// Compiles
ISomeInterface obj1 = (ISomeInterface)t;
// Сompiles too!
SomeClass obj2 = (SomeClass)(object)t;
将在运行时产生相同的错误。
所以原因可能是:编译器不知道哪个接口类实现,但它知道类继承(因此(SomeClass)(object)t
方法工作)。 换句话说:在CLR中禁止无效转换,唯一的区别是在某些情况下它可以在编译时检测到,而在某些情况下 - 不能。 这背后的主要原因是,即使编译器知道所有类的接口,它也不知道它的后代,它可以实现它,并且对于T
是有效的。 考虑以下场景:
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;
}
}
}
我认为转换为接口和转换为类之间的区别在于c#仅支持接口的多个“继承”。 那是什么意思? 编译器只能在编译时确定转换是否对类有效,因为C#不允许对类进行多重继承。
另一方面,编译器在编译时不知道您的类是否实现了转换中使用的接口。 为什么? 有人可以从你的类继承并实现你的演员中使用的接口。 因此,编译器在编译时并未意识到这一点。 (参见下面的SomeMethod4()
)。
但是,如果您的类是密封的,编译器能够确定对接口的强制转换是否有效。
请考虑以下示例:
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!
}
仿制药也是如此。
希望这可以帮助。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.