有关C#语言规范中隐式转换的问题

6.1节隐式 转换因此定义了身份转换

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

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

(在§6.1.6隐式参考转换中)

隐式引用转换是:

  • […]
  • 从任何引用类型引用类型 T如果它具有隐式标识或引用转换为引用类型 T 0并且T 0具有到T的标识转换。

和:

(在§6.1.7拳击转换中)

  • 如果值类型具有到接口类型I 0的装箱转换并且I 0具有到I的标识转换,则值类型具有到接口类型I的装箱转换。

最初它们似乎是多余的(同义词)。 但他们必须在某个目的,所以为什么他们在那里?

你能给出一个T 1T 2这两种类型的例子,如果不是上面引用的段落, T 1不能隐式转换为T 2吗?

规范的第4.7节指出存在从FooFoo的身份转换,反之亦然。 您引用的规范部分是为了确保处理此案例而编写的。 也就是说,如果存在从T到C的隐式引用转换,那么还存在对CCC的隐式引用转换。

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

遗憾的是,该规范并不是一份完美的文件。

2010年9月22日更新:

我怀疑除了蒂姆维之外还有人会读这个。 即便如此,我想对这个答案进行一些修改,因为现在已经接受了一个新的答案,并且辩论仍在继续(至少在我想象的世界中)是否引用了规范的摘录在技​​术上是多余的。 我并没有增加太多,但它太大了,不适合评论。 大部分更新可以在下面的“涉及dynamic类型的转换 ”标题下找到。


2010年9月19日更新:

在你的评论中:

[T]他没有意义。

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

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

不同的前提

首先,你的问题的前提是,如果突出显示的陈述是多余的 ,那么它们就没有用处 。 我的答案的前提是,如果多余的陈述澄清了对每个人都不明显的东西,那么多余的陈述并不一定没有目的。 这些都是矛盾的前提。 如果我们不能就前提达成一致,我们就不能有一个直截了当的逻辑论证。 我只是想让你重新思考你的前提。

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

(顺便说一句,我喜欢你如何让自己成为所有规范读者的代表。)

我完全不能责怪你担任这个职位。 我的意思是,这似乎显而易见。 我在原始答案中没有提供任何具体的例子。 所以下面我将尝试包含一些具体的例子。 但首先,让我退后一步,先说明为什么这个奇怪的身份转换概念首先存在于规范中。

身份转换定义的目的

乍一看,这个定义似乎是多余的; 是不是只是说任何类型T的实例都可以转换为……好吧,到T? 是的。 但我假设*这个定义的目的是为规范提供适当的词汇,以便在讨论转换的过程中利用类型身份的概念。

这允许有关转换的陈述本质上是可传递的。 您从规范中引用的第一点作为重言式声明的一个例子属于这一类。 它表示如果某种类型的隐式转换(我将其称之为K)定义为另一种类型T 0并且T 0 具有到 T 的标识转换 ,那么K可以隐式转换为T.通过给定的标识转换的定义上面,“有一个身份转换为”真正意味着“是同一类型。” 所以声明是多余的

但同样重要的是: 身份转换定义首先存在于为规范配备描述转换的forms语言,而不必说“如果T 0和T真的是同一类型”。

好的,具体例子的时间。

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

注意:Eric Lippert在回答这个问题时提供了一个更好的例子。 我把这前两个例子留作我的观点的一点点补充。 我还添加了第三个例子,它将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; 

显然,事实并非如此。

传递拳击转换

或者采用int类型。 int可以装箱为IComparable ,对吧? 所以这是合法的:

 int i = 10; IComparable x = i; 

现在考虑一下:

 int i = 10; IComparable x = i; 

再次, 是的 ,对于您,我和90%可能遇到它的开发人员来说,这可能是显而易见的 。 但对于那些看不到它的苗条少数人来说:从intIComparable存在一个拳击转换 ,并且存在从IComparableIComparable身份转换 (即IComparableIComparable是相同的类型); 所以从intIComparable存在一个装箱转换。

涉及dynamic类型的转换

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

假设我们有类型MN ,并在某处定义了以下隐式转换:

 public static implicit operator M(N n); 

那么以下是合法的:

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

显然,上述内容远不如前两个例子明显 。 但这是一个百万美元的问题: 即使问题中引用的规范摘录不存在,上述内容仍然合法吗? (为简洁起见,我打算将这些摘录称为Q。 )如果答案是肯定的,那么Q实际上是多余的。 如果不是,那就不是。

我相信答案是肯定的。

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

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

因为objectdynamic被认为是等价的,所以在objectdynamic之间存在身份转换, 并且在用object替换dynamic所有出现时,构造类型之间是相同的[强调我的]

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

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

 M m = n; 

这句话的合法性(无视Q – 记住,正在讨论的问题是如果 Q 存在则上述陈述的假设合法性),因为MN是自定义类型,取决于用户的存在 -定义NM之间的隐式转换。

存在从NM的隐式转换。 根据上面引用的规范部分,在MM之间存在身份转换。 通过身份转换的定义, MM 是相同的类型

所以,就像在前两个(更明显的)示例中一样,我认为即使不考虑Q存在从NM的隐式转换,就像从N存在隐式转换一样。在第一个示例中为K ,并且在第二个示例中,从intIComparable存在装箱转换。

没有Q ,这就不那么明显了(因此Q存在); 但这并不是假的 (即,定义此行为不需要 Q )。 它只是使它不那么明显。

结论

我在原来的答案中说,这是“明显的”解释,因为在我看来,你正在吠叫错误的树。 你最初提出了这个挑战:

你能给出一个T 1 ,T 2这两种类型的例子,如果不是上面引用的段落,T 1就不能隐式转换为T 2吗?

没有人会遇到这个挑战,蒂姆维,因为这是不可能的。 参考第一个关于参考转换的摘录。 假设类型K可以隐式转换为类型T,如果它可以隐式转换为T 0并且T 0与T相同。解构它,将它重新组合在一起,并留下明显的重言式:K如果它可以隐式转换为T,则可以隐式转换为T.这会引入任何新的隐式转换吗? 当然不是。

也许Ben Voigt的评论是正确的; 也许你要问的这些要点会更好地放在脚注中,而不是放在文本正文中。 在任何情况下,我清楚它们多余的,所以从这个前提开始它们不能是多余的,否则就不会有傻瓜的差事。 愿意接受冗余的陈述可能仍然会对一个对每个人都不太明显的概念有所启发,并且接受这些陈述将变得更容易。

冗余? 是。 同义反复? 是。 无意义? 在看来,没有。

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


原始答案

我认为你(可能是故意的)忽略了这里最明显的答案。

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

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

对我来说,这两个句子的含义是一个同义词的说法没有任何意义。 但仅仅因为声明遵循既定的前提逻辑,这并不是每个人都明白的。 换句话说,即使(1)为真,对(2)的答案可能只是: 使阅读规范的任何人都明白所描述的行为

现在你可能会争辩说,即使某些东西不明显 ,如果提供冗余定义,它仍然不属于规范。 对于这个潜在的反对意见,我只能说:做到现实。 在我看来,梳理一份文件,删除所有只是陈述可以从先前陈述中推断出的事实的陈述,这是不切实际的(在我看来)。

如果这一种常见的做法,我想你会发现很多文献 – 不只是规格,而是研究论文,文章,教科书等 – 会更短,更密集,更难理解。

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

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

这表示在C#-land中,1 == 1; 锹是一个锹。 这是将对象引用分配给相同类型的变量的基础; 如果变量类型T1和一个类型T2实际上都是黑桃,则可以将一个分配给另一个,而不必明确地将其中一个作为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 

我知道这听起来很愚蠢,但在某种程度上,必须知道这些事情,而在计算机中,你必须指定一切。 有时读冰岛美国曲棍球规则手册; 本书的第一条规则是游戏应该在冰面上进行。 它是最终的“好伙伴”之一,但也是必须理解的游戏的基本事实,以便任何其他规则有意义。

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

从带有Reflector的mscorlib查看ChangeType的源代码,并注意按原样返回value的条件。

记住a =运算符不是转换,只是参考集。 因此, Client client_2 = client_1类的代码不会执行任何隐式转换。 如果声明了标识隐式转换,则发出错误CS0555

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