转换和使用Convert.To()方法之间的区别

我有一个函数,在string值上施加一个double string值。

 string variable = "5.00"; double varDouble = (double)variable; 

代码更改已签入并且项目生成错误: System.InvalidCastException: Specified cast is not valid.

但是,在做了以下…

 string variable = "5.00"; double varDouble = Convert.ToDouble(variable); 

这个项目没有任何错误

转换和使用Convert.To()方法有什么区别? 为什么抛出一个Exception并使用Convert.To()不?

即使你可能会以某种方式看到它们的目的是完全不同的。 我们首先尝试定义一个演员是什么:

投射是将一种数据types的实体变成另一种数据types的动作。

这是一般的,它在某种程度上等同于一个转换,因为转换通常具有相同的转换语法,所以问题应该是当语言允许强制转换(隐式或显式)以及必须使用(更多)显式转换?

让我先在它们之间一条简单的线,正式地(即使对于语言语法是等价的),一个转换将改变types,而转换将会/可能改变值(最终与types一起 )。 投射也是可逆的,而转换可能不是。

主题很不错,让我们尝试缩小一点,首先我们排除游戏中的自定义演员。

隐式转换

在C#中,如果不会丢失任何信息,隐式强制转换 (请注意,此检查是使用types执行的,而不是实际值 )。

原始types

例如:

 int tinyInteger = 10; long bigInteger = tinyInteger; float tinyReal = 10.0f; double bigReal = tinyReal; 

这些转换是隐含的,因为在转换过程中,不会丢失任何信息(只需要使types变宽)。 反之亦然隐式转换是不允许的,因为无论它们的实际值是什么(因为它们只能在运行时被检查),在转换过程中可能会丢失一些信息。 例如,这段代码不会编译,因为double可能包含(实际上它是)一个不能用float表示的值:

 double bigReal = Double.MaxValue; float tinyReal = bigReal; 

对象

在一个对象(一个指针)的情况下,当编译器可以确定源types是一个派生类(或实现)目标类的types时,转换总是隐含的,例如:

 string text = "123"; IFormattable formattable = text; NotSupportedException derivedException = new NotSupportedException(); Exception baseException = derivedException; 

在这种情况下,编译器知道 string实现了IFormattable ,并且NotSupportedException是(派生自) Exception所以Cast是隐式的。 没有信息丢失,因为对象不会改变它们的types(这是不同的struct和原始types,因为用一个强制types创build另一个types新对象 ),你的观点是什么改变。

显式强制转换

如果转换不是由编译器隐式地完成的,则强制转换是明确的,然后您必须使用转换运算符。 通常这意味着:

  • 您可能会丢失信息或数据,因此您必须注意这一点。
  • 转换可能会失败(因为您不能将一种types转换为另一种types),所以您必须了解自己在做什么。

原始types

原始types需要显式强制转换,因此在转换过程中可能会丢失一些数据,例如:

 double precise = Math.Cos(Math.PI * 1.23456) / Math.Sin(1.23456); float coarse = (float)precise; float epsilon = (float)Double.Epsilon; 

在这两个示例中,即使值落在float范围内,也会丢失信息(在这种情况下为precision),因此转换必须是显式的。 现在试试这个:

 float max = (float)Double.MaxValue; 

这种转换将会失败,所以它必须是明确的,所以你知道它,你可以做一个检查(在这个例子中值是常量,但可能来自一些运行时计算或I / O)。 回到你的例子:

 string text = "123"; double value = (double)text; 

这不会编译,因为编译器不能将文本转换为数字。 文本可能包含任何字符,而不仅仅是数字,这在C#中是太多了,甚至对于一个明确的转换(但是可能允许用另一种语言)。

对象

如果types是不相关的,则从指针(到对象)的转换可能会失败,例如,此代码将无法编译(因为编译器知道没有可能的转换):

 string text = (string)AppDomain.Current; Exception exception = (Exception)"abc"; 

此代码将进行编译,但运行时可能会失败(取决于已InvalidCastException对象的有效types),并带有InvalidCastException

 object obj = GetNextObjectFromInput(); string text = (string)obj; obj = GetNextObjectFromInput(); Exception exception = (Exception)obj; 

转换

所以,最后,如果强制转换,那么为什么我们需要像Convert这样的类呢? 忽略来自Convert实现和IConvertible实现的细微差别,实际上是因为在C#中用一个IConvertible Convert对编译器说:

相信我,即使你现在不知道,这种types也是这种types,让我来做,你会看到。

-要么-

不用担心,我不在乎这个转换会丢失什么。

对于其他任何事情,都需要明确的操作(思考容易转换的含义,这就是为什么C ++为它们引入长,冗长和明确的语法)。 这可能涉及一个复杂的操作(对于string – > double转换,将需要parsing)。 例如,转换为string总是可能的(通过ToString()方法),但它可能意味着与您所期望的不同,所以它必须比一个强制转换更明确( 您写的越多,就越想想自己在做什么 )。

这个转换可以在对象内部(使用已知的IL指令)完成,使用自定义的转换运算符(在类中定义来投射)或更复杂的机制(例如TypeConverter或类方法)。 你不知道会发生什么,但你知道它可能会失败(这就是为什么国际海事组织,当一个更受控制的转换是可能的,你应该使用它)。 在你的情况下,转换只是parsingstring来产生一个double

 double value = Double.Parse(aStringVariable); 

当然,这可能会失败,所以如果你这样做,你应该总是赶上它可能抛出的exception( FormatException )。 这里没有主题,但如果TryParse可用,那么你应该使用它(因为语义上你说它可能不是一个数字,它甚至更快…失败)。

.NET中的转换可以来自很多地方, TypeConverter ,用户定义的转换运算符的隐式/显式转换, IConvertible和parsing方法的实现(我是否忘记了某些东西?)。 看看MSDN的更多细节。

为了完成这个长的答案只是几个关于用户定义的转换运算符的话 让程序员使用一个types转换一个types到另一个types就是 。 这是一个类中的方法(将被铸造的一个),说:“嘿,如果他/她想把这种types转换为那种types,那么我就可以做到”。 例如:

 float? maybe = 10; // Equals to Nullable<float> maybe = 10; float sure1 = (float)maybe; // With cast float sure2 = maybe.Value; // Without cast 

在这种情况下,它是明确的,因为它可能会失败,但这是让实施(即使有关于此的准则)。 想象一下,你写这样一个自定义的string类:

 EasyString text = "123"; // Implicit from string double value = (string)text; // Explicit to double 

在你的实现中,你可能会决定“让程序员的生活更轻松”,并通过一个演员来公开这个转换(记住,这只是一个写less的捷径)。 有些语言甚至可能允许这样做:

 double value = "123"; 

允许隐式转换为任何types(检查将在运行时完成)。 有了正确的选项,这可以在VB.NET中完成。 这只是一个不同的哲学。

我能和他们做什么?

所以最后的问题是你应该什么时候使用这个或另一个。 让我们来看看什么时候可以使用一个明确的演员:

  • 基本types之间的转换。
  • object到任何其他types的转换(这也可能包括拆箱)。
  • 从派生类到基类(或实现的接口)的转换。
  • 通过自定义转换运算符从一种types转换为另一种types。

只有第一次转换可以用Convert完成,所以对于别人你没有select,你需要使用一个明确的转换。

现在让我们看看什么时候可以使用Convert

  • 从任何基本types转换为另一种基本types(有一些限制,请参阅MSDN )。
  • 从任何types的IConvertible为任何其他(支持)types。
  • byte数组与/来自string的转换。

结论

即使转换可以通过转换(除非另有其他可用转换),每次您知道转换可能会失败(因为格式化,因为范围或因为它可能不受支持),所以应使用IMO Convert它清楚了谁会读你的代码,你的意图是什么 ,它可能会失败(简化debugging)。

对于其他所有你需要使用的演员,没有select,但如果另一个更好的方法是可用的,那么我build议你使用它。 在你的例子中,从stringdouble的转换是(特别是如果文本来自用户)经常会失败,所以你应该尽可能的明确(而且你可以更多地控制它),例如使用TryParse方法。

编辑:他们之间有什么区别?

根据更新的问题和保持我之前写的(关于什么时候你可以使用一个演员相比,当你可以/不得不使用Convert )最后要澄清的是,如果他们之间存在差异(此外Convert使用IConvertibleIFormattable接口它可以执行不允许的操作)。

简单的回答是肯定的,他们performance不一样 。 我看到Convert类就像一个辅助方法类,所以经常提供一些好处或稍微不同的行为。 例如:

 double real = 1.6; int castedInteger = (int)real; // 1 int convertedInteger = Convert.ToInt32(real); // 2 

相当不同,对吧? Cast截断(这是我们所期望的),但Convert执行四舍五入到最接近的整数(如果你不知道它可能不会被期望)。 每个转换方法引入差异,所以一般的规则不能应用,他们必须按情况来看… 19基types转换为其他types…列表可以很长,更好地咨询MSDN案例案件!

Casting是告诉编译器的一种方式,“我知道你认为这个variables是一个Bar,但是我碰巧知道的比你更多;这个对象实际上是一个Foo,所以让我把它当作一个Foo来处理从今起。” 然后,在运行时,如果实际对象变成真的是Foo,那么你的代码工作,如果事实certificate该对象根本不是Foo,那么你会得到一个exception。 (特别是一个System.InvalidCastException 。)

换句话说就是“如果你给我一个Bartypes的对象,我可以创build一个全新的Foo对象来表示Bar对象的内容,我不会改变原来的对象,不同的对待原来的对象,它会创build一些新的东西,只是基于其他的价值 。至于如何做,可以是任何东西。在Convert.ToDouble的情况下,它会最终调用Double.Parse有各种各样复杂的逻辑来确定什么types的string代表什么数字值,你可以写你自己的转换方法,映射string加倍不同(也许支持一些完全不同的约定显示数字,如罗马数字或其他)。一个转换可以做任何事情,但想法是你并没有真的要求编译器为你做任何事情;你是编写代码来确定如何创build新的对象,因为编译器,没有你的帮助,没有知道的方法 ow将映射(作为例子)一个string到一个double

那么,你什么时候转换,什么时候转换? 在这两种情况下,我们都有一个types的variables,比如说A,而我们想要一个Btypes的variables。如果我们的A对象实际上在引擎盖下是B,那么我们就投了。 如果它不是真的B,那么我们需要转换它,并且定义程序应该如何从A获得B.

Convert.Double方法实际上只是在内部调用Double.Parse(string)方法。

Stringtypes和Doubletypes都不能在这两种types之间定义显式/隐式转换,所以转换总是失败。

Double.Parse方法将查看string中的每个字符,并根据string中的string值构build一个数字值。 如果任何字符无效,则Parse方法失败(导致Convert.Double方法也失败)。

在你的例子中,你试图将一个string转换为一个double(非整数types)。

它需要一个明确的转换才能工作。

我必须指出,您可以使用Convert.ToDouble而不是Convert.ToInt64因为当您转换为int时,可能会丢失double值的小数部分。

如果你的variables的值是“5.25”,那么varDouble将是5.00(由于转换为Int64而损失0.25)

为了回答你有关铸造vs转换的问题。

你的演员(明确演员)不符合明确演员的要求。 您正在使用转换运算符试图转换的值是无效的(即非整数)。

访问此MSDN页面的铸造/转换规则

铸造不涉及任何转换,即值的内部表示不会改变。 例:

 object o = "Hello"; // o is typed as object and contains a string. string s = (string)o; // This works only if o really contains a string or null. 

你可以把double转换成像这样的string

 double d = 5; string s = d.ToString(); // -> "5" // Or by specifying a format string formatted = d.ToString("N2"); // -> "5.00" 

你可以通过几种方式将string转换为double (这里只是其中的两个):

 string s = "5"; double d = Double.Parse(s); // Throws an exception if s does not contain a valid number 

或者安全的方式

 string s = "5"; double d; if (Double.TryParse(s, out d)) { Console.WriteLine("OK. Result = {0}", d); } else { Console.WriteLine("oops!"); } 

来自MSDN

显式转换(转换):显式转换需要一个转换运算符。 在转换中可能会丢失信息,或者由于其他原因导致转换不成功时,需要投射。 典型的例子包括数值转换为精度较低或范围较小的types,以及将基类实例转换为派生类。

考虑下面的例子:

 double a = 2548.3; int b; b = (int)a; //2548 --> information (.3) lost in the conversion 

并且:

强制转换是显式通知编译器您打算进行转换的一种方式,您知道可能会发生数据丢失。

当您想要在不兼容的types之间进行转换时,可以使用System.Convert类。 转换转换主要区别编译运行时间 。 types转换exception在运行时出现,即在运行时失败的types转换将导致抛出InvalidCastException


结论:在转换过程中,您告诉编译器a实际上是btypes的,如果是这样的话,那么这个项目就不会像这个例子那样产生任何错误:

 double s = 2; int a = (int) s; 

但是在转换中,你正在向编译器说有一种方法可以从btypes创build一个新的对象,请做到这一点,项目构build没有任何错误,但正如我所说, 如果types转换在运行时失败,将导致一个InvalidCastException被抛出

例如,下面的代码从来不会编译,因为编译器检测到不能将DateTimetypes的expression式转换为inttypes:

 DateTime s = DateTime.Now; int a = (int)(s); 

但是这个编译成功了:

 DateTime s = DateTime.Now; int a = Convert.ToInt32(s); 

但是在运行时你会得到InvalidCastException ,它说:

从“DateTime”到“Int32”的转换无效。

 string variable = "5.00"; double varDouble = (double)variable; 

上面的转换根本不被语言所允许。 下面是数值types的显式转换列表: http : //msdn.microsoft.com/en-us/library/yht2cx7b.aspx正如你所看到的,即使不是每个数字types都可以转换为另一种数字types

关于在这里投射的更多信息

这和Convert.ToDouble()有什么不同?

当你施放一个types时,数据结构不会改变。 那么,在数值转换的情况下,你可能会失去几位或附加0位。 但是你仍然在使用一个数字。 你只是改变这个数字所占用的内存量。 这对于编译器来说是足够安全的。

但是当你试图将string转换为数字的时候,你不能这样做,因为改变variables占用的内存量是不够的。 例如, 5.00作为一个string是一个“数字”序列:53(5)46(。)48(0)48(0) – 这是ASCII,但string将包含类似的东西。 如果编译器会从string中取第一个N(4表示双精度不确定)字节 – 那么这个块将包含完全不同的双精度数。 同时Convert.ToDouble()运行一个特殊的algorithm,它将采取一个string的每个符号,找出它代表的数字,并为你做一个双数字,如果string代表一个数字。 大致来说,像PHP这样的语言会在后台为您调用Convert.ToDouble。 但是C#和静态types语言一样,不会为你做。 这可以让你确定任何操作都是types安全的,你不会得到意想不到的事情:

 double d = (double)"zzzz" 

将string转换为不允许的C#这就是为什么你得到一个exception,你需要转换string( MSDN文档显示可接受的转换path)。 这只是因为一个string不一定会包含数字数据,但各种数字types将(禁止空值)。 Convert将运行一个方法,将检查string,看看它是否可以变成一个数字值。 如果可以,那么它将返回该值。 如果不行,就会抛出exception。

要转换它,你有几个select。 你在你的问题中使用了Convert方法,这里的ParseConvert类似,但你也应该看看TryParse ,它可以让你做到:

 string variable = "5.00"; double varDouble; if (Double.TryParse(variable, out varDouble)) { //Code that runs if the conversion succeeded. } else { //Code that runs if the conversion failed. } 

这可以避免可能的exception,如果您尝试ConvertParse非数字string。

double varDouble = (double)variable假定variable已经是double。 如果variable不是double(这是一个string),那么这将失败。 double varDouble = Convert.ToDouble(variable)喜欢它说 – 它转换。 如果它可以parsing或者从variable提取一个double,那么它会。

我第二次使用Double.ParseDouble.TryParse因为它更清楚地表明应该发生什么。 你从一个string开始,并期望它可以转换为一个双。 如果有任何疑问,请使用TryParse

如果variable是方法参数,请将types更改为double。 使呼叫者负责提供正确的types。 编译器就是这样做的。