转换和使用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议你使用它。 在你的例子中,从string
到double
的转换是(特别是如果文本来自用户)经常会失败,所以你应该尽可能的明确(而且你可以更多地控制它),例如使用TryParse
方法。
编辑:他们之间有什么区别?
根据更新的问题和保持我之前写的(关于什么时候你可以使用一个演员相比,当你可以/不得不使用Convert
)最后要澄清的是,如果他们之间存在差异(此外Convert
使用IConvertible
和IFormattable
接口它可以执行不允许的操作)。
简单的回答是肯定的,他们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)
方法。
String
types和Double
types都不能在这两种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
实际上是b
types的,如果是这样的话,那么这个项目就不会像这个例子那样产生任何错误:
double s = 2; int a = (int) s;
但是在转换中,你正在向编译器说有一种方法可以从b
types创build一个新的对象,请做到这一点,项目构build没有任何错误,但正如我所说, 如果types转换在运行时失败,将导致一个InvalidCastException
被抛出 。
例如,下面的代码从来不会编译,因为编译器检测到不能将DateTime
types的expression式转换为int
types:
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
方法,这里的Parse
和Convert
类似,但你也应该看看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,如果您尝试Convert
或Parse
非数字string。
double varDouble = (double)variable
假定variable
已经是double。 如果variable
不是double(这是一个string),那么这将失败。 double varDouble = Convert.ToDouble(variable)
喜欢它说 – 它转换。 如果它可以parsing或者从variable
提取一个double,那么它会。
我第二次使用Double.Parse
或Double.TryParse
因为它更清楚地表明应该发生什么。 你从一个string开始,并期望它可以转换为一个双。 如果有任何疑问,请使用TryParse
。
如果variable
是方法参数,请将types更改为double。 使呼叫者负责提供正确的types。 编译器就是这样做的。