为啥强制转换和转换操作在语法上无法区分?

Posted

技术标签:

【中文标题】为啥强制转换和转换操作在语法上无法区分?【英文标题】:Why are casting and conversion operations are syntactically indistinguishable?为什么强制转换和转换操作在语法上无法区分? 【发布时间】:2015-03-13 16:00:24 【问题描述】:

Stack Overflow 有几个关于强制转换盒装值的问题: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 operator B(A)':用户定义的与 a 之间的转换 不允许派生类

哈!没有歧义!

但是为什么他们允许两个完全不同的操作看起来一样?!原因是什么?

【问题讨论】:

您还可以根据需要创建implicit 转换。那么它将是B b = obj; 首先,这对 Stack Overflow 来说是个糟糕的问题,因为它只能由 C# 1.0 语言设计团队的人员明确回答。我不相信这些人目前在这里回答了很多问题。我很乐意试一试,但我一生都无法弄清楚您在这里实际问的是什么问题。 @EricLippert 为什么强制转换和转换运算符看起来一样? @voroninp 这正是 Eric 的意思——这是一个让人猜测的哲学问题,但除非语言设计者碰巧在场,否则你不会得到任何明确的答案。 @EricLippert。顺便说一句,ECMA-335 不禁止在派生类中转换为基类运算符(即特殊方法)。看起来它在 C# 中被禁止只是为了避免转换和转换操作之间的歧义。 【参考方案1】:

据我所知,您的观察是我在这里所做的观察:

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 中强制转换运算符的历史。在 *** 的答案中,这似乎信息太多了。

坦率地说,最有可能的解释是,这个决定不是不同立场的优点之间长期争论的结果。相反,很可能语言设计团队考虑了它是如何在 C、C++ 和 Java 中完成的,做出了一个看起来不太糟糕的合理妥协立场,然后转向了其他更有趣的业务。因此,正确的答案几乎完全是历史的;为什么 Ritchie 像他为 C 设计的那样设计演员表操作符?我不知道,我们也不能问他。

我给你的建议是不要再问“为什么?”询问有关编程语言设计历史的问题,而不是询问有关特定代码的特定技术问题,这些问题的答案很简短。

【讨论】:

一个有趣的注意事项是,C++ 已不再推荐“C 样式转换”,并添加了几个转换运算符以更明确地说明正在执行的转换类型。 @Guvante:一个很好的观点。然后可以问(对 SO 不利)后续问题“如果这是一个好主意,那么为什么 C# 没有在这里跟随 C++ 的领导?”现在我们有一个“为什么不呢?”关于实现复杂、令人困惑和昂贵的功能的原因的问题。这就是为什么我拒绝回答“为什么”和“为什么不”的问题。 嗯,初衷是想问如何将一个装箱的值转换成特定的类型。例如,当 DataReader[0] 包含一个字节时,我们只能使用 (int)(byte)DataReader[0] 将其转换为 int。然后我发现该要求对所有类型都是通用的,并查看了生成的 IL。 C# 设计者允许不安全的强制转换,但不允许不安全的转换。在我的情况下,我对文章中的前两个有第三种情况:“我有某种类型的值,我希望它有一个转换运算符到我需要的类型。我不在乎它到底是哪种类型。 @voroninp:为从装箱值到其他类型的转换生成代码需要在运行时运行编译器的转换规则;如果这就是你想要做的,那么使用dynamic,或者任何可以做到这一点的辅助方法,并获得性能冲击。 @voroninp:我在原始文章中指出,演员表有两种以上可能的含义;就那篇文章而言,这两个正是我所关心的。【参考方案2】:

转换运算符本质上是“美化的方法调用”,因此编译器(与运行时相反)已经需要知道您要使用转换运算符而不是类型转换。基本上编译器需要检查是否存在转换才能为其生成适当的字节码。

您的第一个代码示例基本上看起来像“从对象转换为 B”,因为编译器不知道变量只能包含 A。根据规则,这意味着编译器必须发出类型转换操作。

您的第二个代码示例对编译器来说是显而易见的,因为“从 A 转换为 B”可以使用转换运算符完成。

【讨论】:

这没有回答“为什么转换和转换操作在语法上无法区分?”的问题 @juharr 然而,这不是一个非常适合 *** 的问题,因为除了猜想之外,除了该语言的设计者之外,没有人能提供。

以上是关于为啥强制转换和转换操作在语法上无法区分?的主要内容,如果未能解决你的问题,请参考以下文章

泛型:强制转换和值类型,为啥这是非法的?

为啥 JavaScript 在连接时优先强制转换为字符串,而在比较时优先转换为数字?

为啥元素搜索反复出现强制转换错误?

C++强制类型转换操作符 dynamic_cast

Java强制转换为啥没有四舍五入

为啥浮动计算和强制转换在调试和发布配置中显示不同的结果?