为什么转换和转换操作在语法上无法区分?
Why are casting and conversion operations are syntactically indistinguishable?
Stack Overflow 有几个关于转换盒装值的问题:1, 2。
解决方案首先需要 unbox
值,然后才将其转换为另一种类型。尽管如此,盒装值 "knows" 是它自己的类型,我看不出为什么不能调用转换运算符。
此外,同样的问题也适用于引用类型:
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)': user-defined conversions to or from a
derived class are not allowed
哈!没有歧义!
但到底为什么他们允许两种截然不同的操作看起来一样?!是什么原因?
转换运算符本质上是 "glorified method calls",因此编译器(相对于运行时)已经需要知道您要使用转换运算符而不是类型转换。基本上,编译器需要检查是否存在转换才能为其生成适当的字节码。
您的第一个代码示例基本上看起来像 "convert from object to B",因为编译器不知道变量只能包含 A。根据规则,这意味着编译器必须发出类型转换操作。
你的第二个代码示例对编译器来说是显而易见的,因为 "convert from A to B" 可以用转换运算符完成。
据我所知,您的观察就是我在这里所做的观察:
http://ericlippert.com/2009/03/03/representation-and-identity/
There are two basic usages of the cast operator in C#:
(1) My code has an expression of type B, but I happen to have more information than the compiler does. I claim to know for certain that at runtime, this object of type B will actually always be of derived type D. I will inform the compiler of this claim by inserting a cast to D on the expression. Since the compiler probably cannot verify my claim, the compiler might ensure its veracity by inserting a run-time check at the point where I make the claim. If my claim turns out to be inaccurate, the CLR will throw an exception.
(2) I have an expression of some type T which I know for certain is not of type U. However, I have a well-known way of associating some or all values of T with an “equivalent” value of U. I will instruct the compiler to generate code that implements this operation by inserting a cast to U. (And if at runtime there turns out to be no equivalent value of U for the particular T I’ve got, again we throw an exception.)
The attentive reader will have noticed that these are opposites. A neat trick, to have an operator which means two contradictory things, don’t you think?
显然您是我召集的“细心的读者”之一,他们注意到我们有一个操作在逻辑上意味着两个截然不同的事情。这是一个很好的观察!
你的问题是“为什么会这样?”这不是一个好问题! :-)
正如我在本网站上多次 提到的那样,我无法满意地回答“为什么”问题。 “因为那是规范所说的”是一个正确的答案,但并不令人满意。真正提问者通常寻找的是语言设计过程的总结。
当 C# 语言设计团队设计功能时,争论可能会持续几个月,他们可能会涉及十几个人讨论许多不同的提案,每个提案都有自己的优缺点,从而产生数百页的笔记。即使我从 1990 年代后期的会议上获得了有关演员表操作的相关信息(我没有),似乎也很难以令原始提问者满意的方式对其进行简洁的总结。
而且,要圆满回答这个问题,当然要从整个历史的角度来讨论。 C# 旨在让现有的 C、C++ 和 Java 程序员立即高效工作,因此它借鉴了这些语言的许多约定,包括转换运算符的基本机制。为了正确回答这个问题,我们还必须讨论 C、C++ 和 Java 中转换运算符的历史。在 Whosebug 上的答案中,这似乎包含太多信息。
坦率地说,最可能的解释是这个决定不是不同立场的长处辩论的结果。相反,语言设计团队可能考虑了如何在 C、C++ 和 Java 中完成,做出了一个看起来不太糟糕的合理妥协立场,然后转向其他更有趣的业务。因此,一个正确的答案几乎完全是历史性的;为什么 Ritchie 像他为 C 所做的那样设计转换运算符?我不知道,也不能问他
我对你的建议是不要再问“为什么?”有关编程语言设计历史的问题,而是询问有关特定代码的特定技术问题,这些问题的答案很简短。
Stack Overflow 有几个关于转换盒装值的问题:1, 2。
解决方案首先需要 unbox
值,然后才将其转换为另一种类型。尽管如此,盒装值 "knows" 是它自己的类型,我看不出为什么不能调用转换运算符。
此外,同样的问题也适用于引用类型:
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)': user-defined conversions to or from a derived class are not allowed
哈!没有歧义!
但到底为什么他们允许两种截然不同的操作看起来一样?!是什么原因?
转换运算符本质上是 "glorified method calls",因此编译器(相对于运行时)已经需要知道您要使用转换运算符而不是类型转换。基本上,编译器需要检查是否存在转换才能为其生成适当的字节码。
您的第一个代码示例基本上看起来像 "convert from object to B",因为编译器不知道变量只能包含 A。根据规则,这意味着编译器必须发出类型转换操作。
你的第二个代码示例对编译器来说是显而易见的,因为 "convert from A to B" 可以用转换运算符完成。
据我所知,您的观察就是我在这里所做的观察:
http://ericlippert.com/2009/03/03/representation-and-identity/
There are two basic usages of the cast operator in C#:
(1) My code has an expression of type B, but I happen to have more information than the compiler does. I claim to know for certain that at runtime, this object of type B will actually always be of derived type D. I will inform the compiler of this claim by inserting a cast to D on the expression. Since the compiler probably cannot verify my claim, the compiler might ensure its veracity by inserting a run-time check at the point where I make the claim. If my claim turns out to be inaccurate, the CLR will throw an exception.
(2) I have an expression of some type T which I know for certain is not of type U. However, I have a well-known way of associating some or all values of T with an “equivalent” value of U. I will instruct the compiler to generate code that implements this operation by inserting a cast to U. (And if at runtime there turns out to be no equivalent value of U for the particular T I’ve got, again we throw an exception.)
The attentive reader will have noticed that these are opposites. A neat trick, to have an operator which means two contradictory things, don’t you think?
显然您是我召集的“细心的读者”之一,他们注意到我们有一个操作在逻辑上意味着两个截然不同的事情。这是一个很好的观察!
你的问题是“为什么会这样?”这不是一个好问题! :-)
正如我在本网站上多次 提到的那样,我无法满意地回答“为什么”问题。 “因为那是规范所说的”是一个正确的答案,但并不令人满意。真正提问者通常寻找的是语言设计过程的总结。
当 C# 语言设计团队设计功能时,争论可能会持续几个月,他们可能会涉及十几个人讨论许多不同的提案,每个提案都有自己的优缺点,从而产生数百页的笔记。即使我从 1990 年代后期的会议上获得了有关演员表操作的相关信息(我没有),似乎也很难以令原始提问者满意的方式对其进行简洁的总结。
而且,要圆满回答这个问题,当然要从整个历史的角度来讨论。 C# 旨在让现有的 C、C++ 和 Java 程序员立即高效工作,因此它借鉴了这些语言的许多约定,包括转换运算符的基本机制。为了正确回答这个问题,我们还必须讨论 C、C++ 和 Java 中转换运算符的历史。在 Whosebug 上的答案中,这似乎包含太多信息。
坦率地说,最可能的解释是这个决定不是不同立场的长处辩论的结果。相反,语言设计团队可能考虑了如何在 C、C++ 和 Java 中完成,做出了一个看起来不太糟糕的合理妥协立场,然后转向其他更有趣的业务。因此,一个正确的答案几乎完全是历史性的;为什么 Ritchie 像他为 C 所做的那样设计转换运算符?我不知道,也不能问他
我对你的建议是不要再问“为什么?”有关编程语言设计历史的问题,而是询问有关特定代码的特定技术问题,这些问题的答案很简短。