转换和转换有什么区别?

Eric Lippert在这个问题上的评论让我感到十分困惑。 C#中的转换和转换有什么区别?

我相信埃里克想说的是:

铸造是描述句法的术语(因此是句法意义)。

转换是一个术语,描述幕后实际采取的行动(以及因此的语义 )。

强制转换expression式用于将expression式显式转换为给定types。

(T)E(其中T是一个types,E是一个一元expression式)的一个强制转换expression式,执行E值到T的显式转换(第13.2节)。

似乎支持说,语法中的转换运算符执行显式转换。

投射是告诉编译器的一种方法:“对象X实际上是Ytypes,继续前进并对待它”。

转换是说:“我知道对象X不是Ytypes,但是存在从Ytypes的X创build一个新对象的方法,请继续执行。

我想起理查德·费曼(Richard Feynman)讲述的一个故事,他在哪里参加一个哲学课,教授向他问道:“费曼,你是一个物理学家,在你看来,电子是一个”基本对象“? 所以费曼问澄清问题“是砖块的一个重要对象?” 去上课。 每个学生对这个问题都有不同的答案。 他们说“砖”的基本抽象概念是重要的客体。 不,一个特定的,独特的砖是必不可less的客体。 不,你可以凭经验观察到的砖块是必不可less的物件。 等等。

这当然不能回答你的问题。

我不打算通过所有这些答案,并与他们的作者辩论我的意思。 我会在几个星期内写一篇关于这个主题的博客文章,我们会看看是否能够说明这个问题。

不过,一个拉费曼的比喻呢。 你希望星期六早上烤一块香蕉面包(因为我几乎每个星期六的早晨都做。)所以你请教了烹饪的喜悦,它说:“等等等等……在另一个碗里,把干燥的食材搅在一起。 ..”

很明显,明天上午你的行为与你的行为之间有很强的关系,但同样很明显,把行动行动混为一谈也是错误的。 指令由文本组成。 它有一个位置,在一个特定的页面上。 它有标点符号。 如果你在厨房里一起搅拌面粉和小苏打,有人问“你现在的标点是什么?”,你可能会认为这是一个奇怪的问题。 动作与指令有关,但指令的文本属性不是动作的属性。

演员阵容不是一个转换,就像一个配方不是烘烤蛋糕的行为一样。 配方是描述行动的文本,然后您可以执行。 转换操作符是描述操作的文本 – 转换 – 然后运行时可以执行。

从C#规范14.6.6:

强制转换expression式用于将expression式显式转换为给定types。

(T)E(其中T是一个types,E是一个一元expression式)的一个强制转换expression式,执行E值到T的显式转换(第13.2节)。

所以cast是一个语法结构,用于指示编译器调用显式的转换。

从C#规范§13:

转换使一种types的expression式被视为另一种types。 转换可以是隐式或显式的,这决定了是否需要显式转换。 [例如,从inttypes到longtypes的转换是隐式的,所以inttypes的expression式可以被隐式地看作longtypes。 从longtypes到inttypes的相反转换是明确的,所以需要显式的转换。

所以转换是实际工作完成的地方。 您会注意到,cast-expression引用表示它执行显式转换,但显式转换是隐式转换的超集,所以您还可以通过cast-expressions调用隐式转换(即使不必)。

只是我的理解,可能太简单了:

当铸造基本数据保持不变(相同的内部表示) – “我知道这是一本字典,但你可以用它作为ICollection”。

转换时,你正在改变内部表示为其他 – “我想这个int是一个string”。

在阅读埃里克的评论之后,

铸造意味着这两种types在某种程度上是相同的。 他们可以实现相同的接口或从相同的基类inheritance,或者目标可以“足够相同”(超集?)投射工作,如从Int16投射到Int32。

然后转换types意味着这两个对象可能足够相似以便被转换。 以一个数字的string表示为例。 它是一个string,它不能简单地被转换成数字,它需要被parsing并从一个转换到另一个,并且,该过程可能失败。 它可能会失败,但我想这是一个更便宜的失败。

这是我认为的两个概念之间的关键区别。 转换将需要某种parsing,或对源数据进行更深入的分析和转换。 铸造不parsing。 它只是在一些多态性级别尝试匹配。

Casting是从另一个types的另一个值创build一个types的值。 转换是一种types的转换,其中值的内部表示也必须改变(而不仅仅是它的解释)。

在C#中,转换和转换都是通过cast-expression来完成的

types一元expression式

区别很重要(注释中提到了这一点),因为只有转换可以由转换运算符声明符创build。 因此,只能在代码中创build(隐式或显式)转换。

非转换隐式转换总是可用于子types到超types转换,并且非转换显式转换总是可用于超types到子types转换。 不允许其他非转换剧组。

在这种情况下,转换意味着您将给定types的对象暴露为其他types的操作,转换意味着您实际上将给定types的对象更改为另一types的对象。

MSDN C#文档的这一页build议转换是特定的转换实例:“显式转换”。 也就是说, x = (int)yforms的转换是一个强制转换。

自动数据types更改(如myLong = myInt )是更通用的“转换”。

强制转换是类/结构体上的运算符。 转换是受影响的类/结构中的一个或另一个上的方法/进程,或者可以是完全不同的类/结构(即Converter.ToInt32()

演员操作有两种:隐式和显式

隐式转换运算符指示一种types(比如Int32)的数据总是可以表示为另一种types(十进制), 而不会丢失数据/精度

 int i = 25; decimal d = i; 

显式的转换运算符指示一个types(十进制)的数据总是可以忠实地表示为另一个types(int),但是可能会丢失数据/精度。 因此,编译器要求您明确声明您已经意识到这一点,并且希望通过使用显式转换语法来完成此操作:

 decimal d = 25.0001; int i = (int)d; 

转换需要两种不一定相关的types,并尝试通过一些过程(如parsing)将其转换为另一种types。 如果所有已知的转换algorithm都失败,则该过程可能会引发exception或返回默认值:

 string s = "200"; int i = Converter.ToInt32(s); // set i to 200 by parsing s string s = "two hundred"; int i = Converter.ToInt32(s); // sets i to 0 because the parse fails 

埃里克对语法转换与语义转换的引用基本上是运算符与方法论的区别。

强制转换是句法的,可能涉及或不涉及转换(取决于转换types)。 如你所知,C ++允许指定你想要使用的types 。

根据你提出的问题(以及他们在说什么语言!),上/下层次结构可能被认为是转换,也可能不被认为是转换

埃里克(C#)是说,铸造到一个不同的types总是涉及转换,虽然转换可能甚至不会改变实例的内部表示。

一个C ++的人会不同意,因为static_cast可能不会导致任何额外的代码(所以“转换”实际上不是真的!

在C#中,Casting和Conversion基本上是相同的概念,只是可以使用Object.ToString()类的任何方法进行转换。 只有在其他职位上描述的铸造操作员(T) E才能进行铸造,并且可以利用转换或装箱。

它使用哪种转换方法? 编译器根据在编译时提供给编译器的类和库来决定。 如果存在隐式转换,则不需要使用转换运算符。 Object o = String.Empty 。 如果只存在显式转换,则必须使用转换运算符。 String s = (String) o

您可以在自己的类中创build explicitimplicit转换运算符。 注意:转换可以使数据看起来非常相似,或者没有像原来的types,但它都是由转换方法定义的,并且使其对编译器是合法的。

铸造总是指使用铸造操作员。 你可以写

 Object o = float.NaN; String s = (String) o; 

但是,如果您访问s ,例如Console.WriteLine ,您将收到一个运行时InvalidCastException 。 所以,演职人员仍然试图在访问时使用转换,但在分配过程中将解决拳击。

插入编辑#2:是不是近乎不一致的近视,因为我提供了这个答案,这个问题已被标记为问题的重复,问:“ 铸造一样的东西转换? ”。 “不”答案压倒性高 。 然而,我在这里的答案指出了为什么演员阵容与转换不一样的生成本质是压倒性的( 然而我在评论中有一个+1 )。 我想读者在理解转换应用于指称语法/语义层并且转换适用于操作语义层的时候有困难。 例如,在另一种数据types中引用(或指向C / C ++的指针)(指向盒装数据types)不会(在所有语言和场景中)生成盒装数据的转换。 例如,在C float a[1]; int* p = (int*)&a; float a[1]; int* p = (int*)&a; 不能保证*p是指int数据。

编译器从指称语义编译为操作语义 。 编译不是双射的,也就是说,不能保证不编译(例如Java,LLVM,asm.js或C#字节码)回到编译为该字节码的任何指示语法(例如Scala,Python,C#,C通过Emscripten,等等)。 因此这两层不一样。

因此,最明显的一个“ 演员 ”和“ 转换 ”是不一样的。 我在这里的回答指出,这些术语适用于两个不同的语义层。 强制转换适用于指示层(编译器的input语法)知道的语义。 转换适用于操作(运行时或中间字节码)层知道什么的语义。 我使用“擦除”的标准术语来描述在操作语义层中没有明确logging的指称语义。

例如,被泛化的generics是在操作语义层中logging指称语义的一个例子,但是它们的缺点是使得操作语义层与高阶指示语义不兼容,例如这就是为什么考虑实现Scala的高级语义是很痛苦的,在C#的CLR上泛化generics,因为generics的C#的指称语义是在操作语义层硬编码的。

加油吧,不要压低那些比你知道得多的人。 投票前先做好功课。


INSERTED EDIT:Casting是一个在指称语义层发生的操作(types以完整的语义expression)。 强制转换可以(例如显式转换)或者不可以(例如向上转换)在运行时语义层上引起转换。 对我的回答(以及对Marc Gavin的评论的赞扬)的积极回应表明,大多数人不了解指称语义和操作(执行)语义之间的区别。 叹。


我将会更简单,更普遍地针对包括C#在内的所有语言阐述Eric Lippert的答案。

强制转换是语法,因此(像所有语法) 在编译时擦除 ; 而转换在运行时引起一些操作


对于我在整个宇宙中知道的每一种计算机语言,这都是一个真实的陈述。 请注意,上面的说法并没有说铸造转换是相互排斥的。

强制 转换可能会在运行时导致转换 ,但在某些情况下可能不会。

因为我们有两个不同的词,即转换转换 ,我们需要一种方法来分别描述语法 (转换运算符)和运行时 (转换,或types检查和可能的转换)中发生了什么。

我们保持这种概念分离是很重要的,因为在某些编程语言中,转换不会导致转换。 也就是说,我们理解隐式投射(例如上传 )只在编译时发生。 我写这个答案的原因是因为我想帮助读者理解计算机语言是多语言的。 还要看看这个通用定义是如何正确应用于C#的情况。

另外,我想帮助读者了解我如何概括概念,帮助我成为计算机语言devise师。 我想传递一种非常简化,抽象的思维方式。 但是我也试图用一种非常实用的方式来解释这一点。 如果需要改进说明,请随时通知我。


Eric Lippert写道:

演员阵容不是一个转换,就像一个配方不是烘烤蛋糕的行为一样。 配方是描述行动的文本,然后您可以执行。 转换操作符是描述操作的文本 – 转换 – 然后运行时可以执行。

配方是语法中发生的事情。 语法总是被清除,并被replace为无或运行时代码。

例如,我可以在C#中编写一个强制 转换,当它不会导致存储要求发生变化或者是向上 转换时,它将在编译时完全擦除。 我们可以清楚地看到,强制转换只是语法,不会改变运行时代码。

 int x = 1; int y = (int)x; Giraffe g = new Giraffe(); Animal a = (Animal)g; 

这可以用于文档目的(但是很吵),但是对于具有types推断的语言来说,它是必不可less的,在这种语言中,有时需要强制转换以告诉编译器您希望推断的types。

例如 ,在Scala中, None的types为Option[Nothing] ,其中Nothing是所有可能types(不是types)的types的底层types。 所以有时候在使用None的时候,types需要被转换成一个特定的types,因为Scala只做本地types推断 ,所以不能总是推断出你想要的types。

 // (None : Option[Int]) casts None to Option[Int] println(Some(7) <*> ((None : Option[Int]) <*> (Some(9) > add))) 

一个强制转换可以在编译时知道它需要types转换,例如int x = (int)1.5 ,或者可能需要在运行时进行types检查和可能的types转换,例如向下转换 。 强制转换(即语法)将被删除并replace为运行时操作。

因此,我们可以清楚地看到,将所有的转换等同于明确的转换,在MSDN文档中是一个含义的错误。 这个文档打算说显式的转换需要一个转换操作符 ,但是它不应该试图暗示所有的转换都是显式的转换。 我相信,Eric Lippert在写他答应答复的博客时可以清楚这一点。


添加 :从评论和聊天中,我可以看到关于删除这个词的含义有一些混淆。

术语“擦除”用于描述在编译时已知的信息,这在运行时是未知的。 例如,types可以用非特定的generics来擦除,这就是所谓的types擦除 。

一般来说,所有的语法都被擦除了,因为CLI一般不是C#的双射(可逆的,一对一的)。 你不能总是从一些任意的CLI代码后退到精确的C#源代码。 这意味着信息已被删除。

那些说擦除的不是正确的术语,是把演员的执行和演员的语义混为一谈。 演员阵营是一个更高层次的语义(我认为它实际上比语法更高,至less在上传和下传的情况下是指称语义),在这个语义水平上说我们要投射这个types。 现在在运行时如何完成是完全不同的语义层次。 在某些语言中,它可能永远是一个NOOP 。 例如,在Haskell中,所有的input信息都会在编译时被清除。