【问题标题】:Question regarding implicit conversions in the C# language specification关于 C# 语言规范中的隐式转换的问题
【发布时间】:2010-09-17 15:38:43
【问题描述】:

第 6.1 节隐式转换这样定义了一个身份转换

身份转换从任何类型转换为相同类型。这种转换的存在使得已经具有所需类型的实体可以说是可转换为该类型。

现在,这样的句子的目的是什么?

(在 §6.1.6 隐式引用转换中)

隐式引用转换为:

  • [...]
  • 从任何 reference-typereference-type T 如果它具有隐式标识或引用转换为 reference-type T0T0 具有到 T 的身份转换。

和:

(在 §6.1.7 装箱转换中)

  • 如果值类型具有到接口类型I0I0 的装箱转换,则它具有到接口类型I 的装箱转换身份转换为I

最初它们似乎是多余的(同义词)。但它们一定是有目的的,所以它们为什么会在那里?

你能举出T1T2这样两种类型的例子吗?T1如果不是上面引用的段落,不会隐式转换为T2

【问题讨论】:

  • 是的,这似乎是同义词。根据“恒等式转换”的定义,“T0 到 T 有恒等式转换”意味着 T0 和 T 是同一类型...
  • 当你需要埃里克·利珀特时他在哪里? :)
  • 在华盛顿西部的集市上,看着漂亮的马匹。

标签: c# language-features implicit-conversion language-specifications


【解决方案1】:

2010 年 9 月 22 日更新:

我怀疑除了 Timwi 之外还有其他人会阅读这篇文章。即便如此,我还是想对这个答案进行一些编辑,因为现在已经接受了一个新的答案,并且关于是否引用的规范摘录的辩论仍在继续(至少在我可能想象的世界中)在技​​术上是多余的。我没有添加太多,但它太重要了,无法放入评论中。大部分更新可以在下面的标题“涉及dynamic 类型的转换”下找到。


2010 年 9 月 19 日更新:

在您的评论中:

[T]这没有意义。

该死的,Timwi,你说很多。但是,好吧,那么;你让我处于守势,所以来吧!

免责声明:我绝对没有像您一样仔细检查了规范。根据您最近的一些问题,您似乎最近一直在研究它。这自然会让你比 SO 上的大多数用户更熟悉很多细节;因此,就像您可能从 Eric Lippert 以外的任何人那里收到的大多数答案一样,这可能不会让您满意。

不同的前提

首先,您的问题的前提是,如果突出显示的语句是冗余,那么它们没有目的。我的回答的前提是,如果冗余陈述澄清了对每个人都不是很明显的事情,那么它们不一定没有目的。这些都是矛盾的前提。如果我们不能在前提上达成一致,我们就不能有一个直截了当的逻辑论证。我只是要求你重新考虑你的前提。

然而,你的回答是重申你的前提:“如果这些句子真的是多余的,那么它们只会让读者感到困惑,而不会澄清任何事情。”

(顺便说一句,我喜欢你将自己设置为所有规范读者的代表。)

确切地说,我不能责怪你担任这个职位。我的意思是,它确实似乎很明显。而且我在最初的答案中没有给出任何具体的例子。所以下面我将尝试包括一些具体的例子。但首先,让我退后一步,谈谈为什么这个奇怪的身份转换概念首先存在于规范中。

身份转换的目的定义

乍一看,这个定义似乎是多余的;不是说任何类型 T 的实例都可以转换为……嗯,转换为 T 吗?是的。但我假设*此定义的目的是为规范提供适当的词汇表,以便在讨论 conversions 的上下文中利用 type identity 的概念。

这允许关于本质上具有传递性的转换的陈述。您从规范中引用的第一点作为重言式陈述的示例属于这一类。它表示,如果为某种类型(我称之为 K)定义了隐式转换为另一种类型 T0 并且 T0 具有到的身份转换 T,则 K 可以隐式转换为 T。根据上面给出的恒等转换的定义,“具有恒等转换”实际上意味着“与类型相同”。所以这个语句是冗余的

但同样:身份转换定义的存在首先为规范配备了一种描述转换的正式语言,而不必说诸如“如果T0 和 T 真的是同一类型。”

好的,具体例子的时间到了。

隐式转换的存在可能一些开发人员来说并不明显

注意:Eric Lippert 在his answer to the question 中提供了一个更好的示例。我将前两个例子作为对我观点的次要强化。我还添加了第三个示例,具体化了 objectdynamic 之间存在的身份转换,正如 Eric 的回答中所指出的那样。

传递引用转换

假设您有两种类型,MN,并且您有一个这样定义的隐式转换:

public static implicit operator M(N n);

然后你可以这样写代码:

N n = new N();
M m = n;

现在假设你有一个文件,上面有这个using 声明:

using K = M;

然后你有,在文件后面:

N n = new N();
K k = n;

好的,在我继续之前,我意识到这对来说是显而易见的。但我的答案是,并且来自一开始,它可能不是每个人来说都是显而易见的,因此指定它——而冗余——仍然有一个目的.

那个目的是:让任何摸不着头脑的人看清楚代码,这是合法的。从N到M存在隐式转换,从M到K存在恒等转换(即M和K是同一类型);所以存在从 N 到 K 的隐式转换。它不是 只是 合乎逻辑的(尽管它可能是 合乎逻辑的); 它就在规范中。否则,人们可能会错误地认为需要以下内容:

K k = (M)n;

显然不是。

传递性装箱转换

或者输入intint 可以装箱为IComparable<int>,对吗?所以这是合法的:

int i = 10;
IComparable<int> x = i;

现在考虑一下:

int i = 10;
IComparable<System.Int32> x = i;

再次,是的,对于您、我和 90% 可能遇到过它的所有开发人员来说,这可能是显而易见的。但是对于那些没有立即看到它的少数人来说:boxing 转换intIComparable&lt;int&gt;身份转换IComparable&lt;int&gt;IComparable&lt;System.Int32&gt;(即IComparable&lt;int&gt;IComparable&lt;System.Int32&gt;是同一类型);所以存在从intIComparable&lt;System.Int32&gt; 的拳击转换。

涉及dynamic 类型的转换

我将从上面的参考转换示例中借用并稍微调整一下,以说明规范 4.0 版本中 objectdynamic 之间的身份关系。

假设我们有 M&lt;T&gt;N 类型,并在某处定义了以下隐式转换:

public static implicit operator M<object>(N n);

那么以下是合法的:

N n = new N();
M<dynamic> m = n;

很明显,上面的例子远没有前面两个例子那么明显。但这是一个价值百万美元的问题:即使问题中引用的规范摘录不存在,上述仍然是否合法?(我将致电这些摘录 Q 为简洁起见。)如果答案是肯定的,那么 Q 实际上是多余的。如果不是,则不是。

我相信答案是肯定的。

考虑在第 6.1.1 节中定义的身份转换的定义(我在这里包括了整个部分,因为它很短):

身份转换从任何类型转换为相同类型。这种转换的存在使得已经具有所需类型的实体可以说是可转换为该类型。

因为objectdynamic 被认为是等效的,所以objectdynamic 之间以及构造类型之间存在身份转换,当用@987654358 替换所有出现的dynamic 时相同的构造类型之间@。 [强调我的]

(最后一部分也包含在第 4.7 节中,它定义了dynamic 类型。)

现在让我们再看一遍代码。特别是我对这一行感兴趣:

M<dynamic> m = n;

此声明的合法性(忽略 Q -- 请记住,正在讨论的问题是上述声明的假设合法性 if Q 存在),因为M&lt;T&gt;N是自定义类型,取决于NM&lt;dynamic&gt;之间是否存在用户定义的隐式转换。

存在从NM&lt;object&gt; 的隐式转换。根据上面引用的规范部分,M&lt;object&gt;M&lt;dynamic&gt; 之间存在身份转换。根据身份转换的定义,M&lt;object&gt;M&lt;dynamic&gt;属于同一类型

因此,就像前两个(更明显的)示例一样,我相信确实存在从 NM&lt;dynamic&gt; 的隐式转换即使没有考虑 Q帐户,正如在第一个示例中确实存在从 NK 的隐式转换,并且在第二个示例中存在从 intIComparable&lt;System.Int32&gt; 的装箱转换。

没有Q,这将不那么明显(因此Q的存在); 但这并不意味着它是假的(即,Q 不是必要来定义此行为)。它只是让它变得不那么明显。

结论

我在最初的回答中说这是“显而易见的”解释,因为在我看来,你在找错树了。您最初提出了这个挑战:

您能否举一个 T1、T2 两种类型的示例,使得 T1 不会隐式转换为 T2 如果不是上面引用的段落?

蒂姆维,没有人会迎接这个挑战,因为这是不可能的。取第一个关于参考转换的摘录。就是说如果类型 K 可以隐式转换为 T0 并且 T0 与 T 相同,则类型 K 可以隐式转换为类型 T。解构 this,把它回到一起,你会留下一个明显的重言式:如果 K 可以隐式转换为 T,则 K 可以隐式转换为 T。这会引入任何新的隐式转换吗?当然不是。

所以也许 Ben Voigt 的评论是正确的;也许您要询问的这些要点最好放在脚注中,而不是放在正文中。无论如何,我很清楚它们 是多余的,因此从前提开始它们不能是多余的,否则它们就不会在那里做傻事。愿意接受多余的陈述可能仍会阐明对每个人来说可能并不明显的概念,并且更容易接受这些陈述的本来面目。

冗余?是的。重言式?是的。无意义?在我的看来,没有。

*显然,我没有参与编写 C# 语言规范。如果我这样做了,这个答案将更具权威性。事实上,它只是代表一个人试图理解一份相当复杂的文件的微弱尝试。


原答案

我认为您(也许是故意)忽略了这里最明显的答案。

在你的问题中考虑这两个句子:

(1) 最初它们似乎是多余的 (同义词)。 (2) 但他们必须在那里 出于某种目的,那么他们为什么会在那里?

对我来说,这两个句子的含义是重复的陈述没有任何意义。但是仅仅因为一个陈述在逻辑上是从既定的前提中得出的,这并不能让每个人都明白这一点。换句话说,即使 (1) 为真,(2) 的答案可能只是:让阅读规范的任何人都清楚描述的行为。

现在您可能会争辩说,即使某些东西显而易见,如果它提供了冗余定义,它仍然不属于规范。对于这种潜在的反对意见,我只能说:现实一点。梳理一份文件,剔除所有陈述,这些陈述只是陈述可能从先前陈述中推导出来的事实,这并不实际(在我看来)。

如果这一种常见的做法,我想你会发现很多文献——不仅仅是规范,还有研究论文、文章、教科书等——将是一个更短、更密集、更难理解。

所以:是的,也许它们是多余的。但这并不能否定他们的目的。

【讨论】:

  • 这就是标准有脚注的原因:指出规范规范隐含但可能不明显的东西。
  • @Ben:好的,但问题似乎很简单:“为什么这些陈述在规范正文中而不是在脚注中?” ——这似乎本质上是一个风格问题。 (但是,我从来没有写过规范,所以也许我只是不明白一些现有标准在这个领域的重要性?)
  • 对不起,丹,但这没有意义。如果这些句子真的是多余的,那么它们只会使读者感到困惑,而不会澄清任何事情;所有其他要点(我没有引用)都更加清晰,涵盖了所有明显和几个不明显的情况。如果我引用的那些没有涵盖一个非显而易见的案例,它们就不会存在。我在问这个不明显的案例是什么。
  • @Timwi:我的观点是冗余和明显不是一回事。您只是坚持认为他们是,并得出结论认为我的回答没有意义。我更新了一些我认为规范中定义的行为可能每个人都不明显的案例示例。但是,您必须保持开放的心态,因为我认为您(或我)将我的任何一个示例都视为不明显的事情将是一种延伸。但这只是说明我(无法)提出好的例子的能力,而不是我的基本观点:多余!=毫无意义。
  • 我不得不说,我对您为更新该答案所付出的努力感到惊讶和印象深刻。我认为你应该为此获得一些布朗尼积分。此外,我没有意识到我所说的“没有意义”的说法是如此有争议:如果它伤害了任何人的感受,我道歉。 ——尽管如此,我的直觉仍然告诉我,这些陈述是有实际目的的,并不是多余的,正如你从 Eric Lippert 现在出现的答案中看到的那样,我是对的。
【解决方案2】:

规范的第 4.7 节指出,存在从 Foo&lt;dynamic&gt;Foo&lt;object&gt; 的身份转换,反之亦然。您引用的规范部分是为了确保处理这种情况而编写的。也就是说,如果存在从 T 到 C&lt;object, object&gt; 的隐式引用转换,那么还有到 C&lt;object, dynamic&gt;C&lt;dynamic, object&gt;C&lt;dynamic, dynamic&gt; 的隐式引用转换。

有人可能会合理地指出 (1) 这些短语的意图不明显 - 因此您的问题 - 并且令人困惑,并且 (2) 关于身份转换的部分应该交叉引用关于动态转换的部分,以及 ( 3) 规范中这样的短语使规范的实现者很难将规范语言清楚地翻译成实现。如何知道是否存在任何此类类型?规范不需要指定确切的算法,但如果能提供更多指导就更好了。

遗憾的是,该规范并不是一个完美的文档。

【讨论】:

  • @Eric — 你是否也有对my Expression Tree question 的回复?我意识到这更像是一个框架问题而不是语言设计问题。
  • @Timwi:我已经这样做了。我注意到 C# 团队设计并实现了框架的这一部分,因为它是由语言设计直接驱动的。 (从那时起,DLR 团队接管了增强功能。)
  • @Eric — 我也有同样的怀疑,但从之前的经验来看,如果我不对此做出假设或推测性猜测,您更愿意... :-)
  • @Timwi:做出你喜欢的所有假设和推测。明确一点:我反对的是你告诉我的客户不要向我报告我的错误。
  • @Eric — 好的。感谢您清除它。我讨厌打扰,但我确实通过您博客上的 Web 表单向您报告了 C# 语言规范中的一个小错误(关于类型推断部分中“依赖”定义的传递性)——您收到了吗?我希望得到回应,因为那样我会报告更多。 :)
【解决方案3】:

身份转换将从 任何类型到同一类型。这 存在转换使得一个实体 已经有所需类型的可以 据说可以兑换成 输入。

这表示在 C#-land 中,1==1;锹是锹。这是将对象引用分配给相同类型的变量的基础;如果一个类型为 T1 的变量和一个类型为 T2 的变量实际上都是 Spade,则可以将一个分配给另一个,而不必将一个显式转换为 Spade。想象一个 C# 变体,其中赋值必须如下所示:

Spade mySpade = new Spade();
Spade mySpade2;

mySpade2 = (Spade)mySpade; //explicit cast required

此外,数学中的“恒等式”表明,在给定一组输入的情况下计算结果的表达式等同于在给定相同输入的情况下产生相同结果的另一个表达式。在编程中,这意味着计算结果为类型实例的表达式或函数等效于该类型,无需显式转换。如果不成立,则需要以下代码:

public int myMethod() { /*Do something*/ }
...
int myInt = (int)myMethod(); //required even though myMethod() evals to an int.
...
int myInt = (int)(1 + 2); //required even though 1, 2, and 1+2 eval to an int.

第二条规则基本上是说,一个值类型可以分配给一个类的成员变量,如果部分地,成员变量(根据定义是装箱类型,因为它的容器是引用类型)被声明为同类型。如果未指定此规则,理论上,可能存在一个 C# 版本,其中必须将纯值类型显式转换为它们的引用模拟,以便将其存储为类的成员变量。例如,想象一个 C# 版本,其中蓝色关键字类型(int、float、decimal)和浅蓝色类名(Int32、Float、Decimal)指的是两个非常不同的、只能显式转换的类型,而 int 、float、decimal 等作为成员变量类型是不合法的,因为它们不是引用类型:

public class MyClass
{
  Int32 MyBoxedValueType; //using "int" not legal
}

...

MyClass myClass = new MyClass();
int theInt = 2;

myClass.MyBoxedValueType = (Int32)theInt; //explicit cast required

我知道这听起来很傻,但在某种程度上,这些事情必须是已知的,而在计算机中,你必须指定一切。有时阅读美国冰球规则手册了解冰球;书中的第一条规则是游戏必须在冰面上进行。它是终极的“废话”之一,也是游戏的基本真理,必须理解才能使任何其他规则有意义。

【讨论】:

  • KeithS,非常感谢您的回答,非常感谢您为此付出的努力。但不幸的是,你还没有回答我的问题。您给出的所有转换都包含在规范中的其他点中。例如,到 objectValueType 的隐式转换已经在第 6.1.7 节的第一段中涵盖:“从任何 non-nullable-value-type 到 @ 987654326@ 和 dynamic,到 System.ValueType 以及由 non-nullable-value-type 实现的任何 interface-type。此外,enum-type 可以转换为 System.Enum 类型。”
  • 从类到基类以及从类到它实现的接口的转换,在第 6.1.6 节的第二个和第三个要点中涵盖:“• 从任何 类-type S 到任何 class-type T,前提是 S 派生自 T。 • 从任何类类型 S 到任何接口类型 T,只要S 实现T。”
  • @Timwi:好的,但规则仍然说 1==1;存在一种类型和相同类型之间的隐式转换,即Spade newSpade = theSpade;是合法的,无需将 theSpade 转换为 Spade(如果它已经是一个)。装箱规则规定值类型可以装箱为相同定义类型的参考模拟;堆栈上的 int 可以分配为引用类型的 int 成员变量。这两个规则定义了允许基于继承的隐式转换的基础;如果你必须对自身施放 Spade,则不能隐式施放 CardSuit。
  • @KeithS:抱歉,你不知道你在说什么。引用类型中int 类型的成员变量不进行装箱。在 C# 中有“相同定义类型的引用模拟”这样的东西——如果它与值类型是相同的类型,那么它显然是一个值类型并且它没有被装箱。正如我已经提到的,基于继承的转换已经在别处定义了。你根本没有回答我的问题。
  • 你是不知道你在说什么的人。作为引用类型成员的值类型按定义装箱;它与实例的其余状态一起存储在堆中,而不是像本地值类型那样存储在堆栈中。我想说的是,如果没有这条规则明确说明一个类型可以被隐式装箱,就必须进行显式转换才能将装箱变量分配给未装箱变量,反之亦然。存在这种隐式转换的事实是没有单独的“参考模拟”类型的原因。
【解决方案4】:

无论是否实现了IConvertible,代码在像Convert.ChangeType(client, typeof(Client)) 这样调用时都可以保证传递。

使用 Reflector 从mscorlib 中查看ChangeType 的来源,注意value 按原样返回的条件。

记住= 运算符不是转换,只是一个引用集。所以像Client client_2 = client_1 这样的代码不会执行任何隐式转换。如果声明了身份隐式转换,则会发出错误CS0555

我猜规范说让 C# 编译器处理这种情况,因此 dot not 手动尝试定义身份转换。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-20
    • 2016-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多