堆栈溢出有几个有关强制转换框值的问题:1,2。
该解决方案首先需要将值unbox
转换为另一种类型。但是,盒装值“知道”它自己的类型,并且我看不出为什么不能调用转换运算符。
此外,相同的问题对于引用类型也有效:
void Main()
{
object obj = new A();
B b = (B)obj;
}
public class A
{
}
public class B {}
此代码引发
InvalidCastException
。因此,值与引用类型无关。这就是编译器的行为方式。对于上面的代码,它发出
castclass B
,对于代码void Main()
{
A obj = new A();
B b = (B)obj;
}
public class A
{
public static explicit operator B(A obj)
{
return new B();
}
}
public class B
{
}
它发出
call A.op_Explicit
。啊哈!在这里,编译器会看到
A
有一个运算符并对其进行调用。但是,如果B
从A
继承,会发生什么呢?没那么快,编译器非常聪明……它只是说:A.explicit运算符B(A)':用户定义的与a之间的转换
不允许派生类
哈!没有歧义!
但是为什么他们在地球上却允许两个完全不同的操作看起来相同?是什么原因
最佳答案
据我所知,您的观察是我在这里所做的观察:
http://ericlippert.com/2009/03/03/representation-and-identity/
C#中的强制转换运算符有两种基本用法:
(1)我的代码具有类型B的表达式,但是我碰巧拥有比编译器更多的信息。我声称可以肯定地知道,在运行时,类型B的对象实际上始终将是派生类型D。我将通过在表达式上插入D的类型来告知编译器该声明。由于编译器可能无法验证我的主张,因此编译器可能会通过在我提出主张的位置插入运行时检查来确保其准确性。如果我的主张不正确,则CLR将引发异常。
(2)我有一个T类型的表达式,我肯定知道它不是U类型。但是,我有一种众所周知的方式将T的某些或全部值与U的“等效”值相关联。指示编译器通过将强制转换插入到U中来生成实现此操作的代码。(如果在运行时发现对于我得到的特定T没有等效的U值,则再次引发异常。)
细心的读者会注意到这些是相反的。一个巧妙的技巧,要有一个操作员意味着两个矛盾的事情,您不觉得吗?
因此,显然您是我召集的“专注读者”之一,他们注意到我们执行的一个操作从逻辑上讲意味着两种截然不同的事情。这是一个很好的观察!
您的问题是“为什么会这样?”这不是一个好问题! :-)
正如我在该站点上多次提到的那样,我无法令人满意地回答“为什么”问题。 “因为这就是规范所说的话”是正确的答案,但并不令人满意。实际上,发问者通常正在寻找的是语言设计过程的摘要。
当C#语言设计团队设计功能时,辩论可能会持续数月之久,他们可能会涉及十几个人,讨论许多不同的建议,各有各的优缺点,产生数百页的注释。即使我从1990年代末的会议上获得了有关演职运作的相关信息,而我没有,但似乎很难以一种使原始提问者满意的方式进行简要总结。
此外,为了令人满意地回答这个问题,当然必须讨论整个历史观点。 C#旨在为现有的C,C ++和Java程序员立即提高生产力,因此它借鉴了这些语言的许多约定,包括其用于转换运算符的基本机制。为了正确回答这个问题,我们还必须讨论C,C ++和Java中的强制转换运算符的历史。在StackOverflow的答案中,似乎提供了太多信息。
坦率地说,最有可能的解释是,这一决定并不是长期以来不同立场的优劣辩论的结果。相反,语言设计团队很可能考虑过如何用C,C ++和Java完成工作,做出了看起来不太糟糕的合理折衷方案,并转向了其他更有趣的业务。因此,正确的答案几乎完全是历史性的;为什么Ritchie像他为C一样设计转换运算符?我不知道,我们不能问他。
我对您的建议是您不要再问“为什么?”有关编程语言设计历史的问题,而是询问有关特定代码的特定技术问题,即答案简短的问题。