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

时间:2013-03-13 18:55:28

标签: c# casting

我有一个在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()不会?

10 个答案:

答案 0 :(得分:90)

即使你可能以某种方式看待它们,它们的目的也完全不同。让我们首先尝试定义演员阵容:

  

强制转换是将一种数据类型的实体更改为另一种数据类型的操作。

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

让我首先绘制它们之间的简单线条。形式上(即使语言语法等效),转换将改变类型,而转换将/可能改变值(最终一起与类型)。演员也是可逆的,而转换可能不是。

这个主题非常广泛,所以让我们尝试通过从游戏中排除自定义强制转换运算符来缩小范围。

隐式演员阵容

在C#中,当您赢得任何信息时,隐含<强>;请注意,此检查是与类型相关而非实际值执行的

原始类型

例如:

int tinyInteger = 10;
long bigInteger = tinyInteger;

float tinyReal = 10.0f;
double bigReal = tinyReal;

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

// won't compile!
double bigReal = Double.MaxValue;
float tinyReal = bigReal;

物件

如果是对象(指向),当编译器可以确定源类型是派生类(或它实现)目标类的类型时,强制转换总是隐式的,例如:

string text = "123";
IFormattable formattable = text;

NotSupportedException derivedException = new NotSupportedException();
Exception baseException = derivedException;

在这种情况下,编译器知道 string实现IFormattableNotSupportedException是(派生自)Exception所以强制转换是隐含的。没有信息丢失,因为对象没有改变它们的类型(这与struct和原始类型不同,因为使用强制转换创建另一种类型的新对象),更改是您的视图

显式强制转换

当转换未由编译器隐式完成时,转换是显式的,然后必须使用转换运算符。通常意味着:

  • 您可能会丢失信息或数据,因此您必须了解它。
  • 转换可能会失败(因为您无法将一种类型转换为另一种类型),因此,您必须再次了解自己正在做的事情。

原始类型

在转换过程中,原始类型需要显式强制转换,否则可能会丢失一些数据,例如:

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

float epsilon = (float)Double.Epsilon;

在这两个示例中,即使值落在float范围内,您也会丢失信息(在本例中为精度),因此转换必须是明确的。现在试试这个:

float max = (float)Double.MaxValue;

此转换将失败,因此,它必须是明确的,以便您了解它并且您可以进行检查(在示例值中是常量,但它可能来自某些运行时计算或I / O)。回到你的例子:

// won't compile!
string text = "123";
double value = (double)text;

这不会编译,因为编译器无法将文本转换为数字。文本可能包含任何字符,而不仅仅是数字,这在C#中太多了,即使对于显式转换(但可能允许用其他语言)。

物件

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

// won't compile!    
string text = (string)AppDomain.Current;
Exception exception = (Exception)"abc";

此代码将编译,但在运行时可能会失败(取决于已转换对象的有效类型)InvalidCastException

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

obj = GetNextObjectFromInput();
Exception exception = (Exception)obj;

转化

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

  

相信我,即使你现在还不知道,这种类型就是那种类型,让我这样做,你就会看到。

-OR -

  

别担心,我不在乎这件转换会丢失一些东西。

对于其他任何事情,需要更多显式操作(考虑 easy casts 的含义,这就是为什么C ++为它们引入冗长,冗长和明确的语法的原因)。这可能涉及复杂的操作(对于string - &gt; double转换,将需要解析。例如,转换为string始终是可能的(通过ToString()方法),但它可能意味着与您期望的不同,因此它必须比投射更明确(您写的更多,你更多地考虑你正在做什么)。

此转换可以在对象内部完成(使用已知的IL指令),使用自定义转换运算符(在类中定义要转换)或更复杂的机制(例如TypeConverter或类方法) 。你不知道会发生什么事情,但你知道它可能会失败(这就是为什么IMO可以进行更多控制转换时你应该使用它)。在您的情况下,转化只会解析string以生成double

double value = Double.Parse(aStringVariable);

当然这可能会失败,所以如果你这样做,你应该总是抓住它可能抛出的异常(FormatException)。它不在这里,但如果TryParse可用,那么你应该使用它(因为在语义上你它可能不是一个数字而且它甚至是更快......失败)。

.NET中的转换可以来自很多地方,TypeConverter,使用用户定义的转换运算符的隐式/显式转换,IConvertible的实现和解析方法(我忘了什么?)。有关它们的更多详细信息,请查看MSDN。

要完成这个长时间的答案,只需几句关于用户定义的转换运算符。它只是 sugar 让程序员使用强制转换将一种类型转换为另一种类型。它是一个类(一个将被投射的类)中的一个方法,它说&#34;嘿,如果他/她想要将这种类型转换为那种类型,那么我可以做到这一点#34;。例如:

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

在这种情况下,它是明确的,因为它可能会失败,但这是让实现(即使有关于此的指导)。想象一下,你编写一个这样的自定义字符串类:

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

在您的实施中,您可能决定让程序员的生活更轻松,并且#34;并通过演员来揭示这种转换(记住它只是一个写得更少的捷径)。有些语言甚至可以允许这样做:

double value = "123";

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

我该怎么办?

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

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

只能使用Convert进行第一次转换,因此对于其他人,您别无选择,需要使用显式转换。

现在让我们看看你何时可以使用Convert

  • 从任何基本类型转换为其他基本类型(有一些限制,请参阅MSDN)。
  • 从实现IConvertible到任何其他(支持)类型的任何类型的转换。
  • byte数组转换为/从字符串转换。

结论

每次你知道转换可能失败时都应该使用IMO Convert(因为格式,因为范围或因为它可能不受支持),即使可以使用强制转换进行相同的转换(除非某事别的可用)。 明确知道谁会在您的代码中读取您的意图,并且可能会失败(简化调试)。

对于其他一切你需要使用演员,没有选择,但如果有另一种更好的方法,那么我建议你使用它。在您的示例中,从stringdouble的转换(特别是如果文本来自用户)经常会失败,因此您应该尽可能明确地进行转换(此外,您可以更好地控制它) ),例如使用TryParse方法。

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

根据更新的问题并保留我之前写的内容(关于何时你可以使用演员表比你何时/必须使用Convert),最后一点澄清是它们之间存在差异(此外Convert使用IConvertibleIFormattable接口,因此它可以执行强制转换不允许的操作。)

简短回答是是的,他们表现不同。我看到Convert类就像一个辅助方法类,因此它经常提供一些好处或略有不同的行为。例如:

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

非常不同,对吗?转换截断(它是我们所期望的)但是Convert执行舍入到最接近的整数(如果您不知道它,这可能不会被预期)。每种转换方法都会引入差异,因此无法应用一般规则,必须逐个查看... 19种基本类型转换为每种其他类型...列表可能相当长,更好地咨询MSDN逐个案例!

答案 1 :(得分:12)

Casting是告诉编译器的一种方式,“我知道你认为这个变量是一个Bar,但我碰巧知道的比你多;对象实际上是一个Foo,所以让我把它看作是它从现在开始,Foo。“然后,在运行时,如果实际对象真的是一个Foo然后你的代码工作,如果事实证明该对象根本不是Foo,那么你得到一个例外。 (特别是System.InvalidCastException。)

转换另一方面是一种说法,“如果你给我一个Bar类型的对象,我可以创建一个全新的Foo对象来表示该Bar对象中的内容。我不会改变原始对象,它不会以不同的方式处理原始对象,它将创建一些基于其他值的新东西。至于它将如何做,它可以是任何东西。在{{{ 1}}它最终会调用Convert.ToDouble,它有各种复杂的逻辑来确定哪些类型的字符串代表什么数值。你可以编写自己的转换方法,将字符串映射为双倍不同(也许完全支持一些显示数字的不同惯例,例如罗马数字或其他任何东西。转换可以做任何事情,但想法是你并不是真的要求编译器为你做任何事情;你是编写代码以确定如何做的人创建新对象,因为编译器在没有您的帮助的情况下无法知道如何操作将Double.Parse映射(例如)到string

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

答案 2 :(得分:4)

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

String类型和Double类型都没有定义两种类型之间的显式/隐式转换,因此转换将始终失败。

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

答案 3 :(得分:3)

在您的示例中,您尝试将字符串转换为double(非整数类型)。

需要进行显式转换才能正常工作。

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

如果您的变量的值为“5.25”,则varDouble将为5.00(由于转换为Int64而损失0.25)

回答关于投射与转换的问题。

您的演员(显式演员)不符合明确演员的要求。您尝试使用强制转换运算符强制转换的值无效(即非整数)。

有关投射/转化的规则,请访问此MSDN Page

答案 4 :(得分:3)

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

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!");
}

答案 5 :(得分:3)

来自MSDN

  

显式转换(强制转换):显式转换需要强制转换运算符。当转换中可能丢失 信息时,或者由于其他原因转换可能无法成功时,需要进行转换。典型示例包括将数值转换为精度较低或范围较小的类型,以及将基类实例转换为派生类。

考虑以下示例:

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

还有:

  

强制转换是一种明确告知编译器您打算进行转换的方法,并且您知道可能会发生数据丢失。

如果要在不兼容的类型之间进行转换,可以使用System.Convert课程。 投射转换之间的主要区别编译运行时。类型转换异常出现在运行时,即在运行时失败的类型转换将导致InvalidCastException被抛出。

<小时/> 结论:在转换中,您告诉编译器a实际上是类型b,如果是这样,项目构建没有任何错误,如下例所示:

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

但是在转换中你对编译器说有一种方法可以从类型为a的{​​{1}}创建一个新对象,请执行它并投影构建没有任何错误但是我说如果在运行时类型转换失败,它将导致b被抛出

例如,下面的代码永远不会编译,因为编译器检测到无法将InvalidCastException类型的表达式转换为DateTime类型:

int

但是这个编译成功了:

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

但在运行时,您会得到DateTime s = DateTime.Now; int a = Convert.ToInt32(s); ,其中包含:

  

来自&#39; DateTime&#39;的无效演员表到&#39; Int32&#39;。

答案 6 :(得分:1)

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

语言不允许进行上述转换。以下是数值类型的显式强制类型列表:http://msdn.microsoft.com/en-us/library/yht2cx7b.aspx如您所见,即使不是每种数字类型都可以转换为其他数字类型

有关投射here

的更多信息
  

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

转换类型时,不会更改数据结构。好吧,在数值转换的情况下,你可能会丢失一些位或少几个额外的0位。 但您仍在使用数字。您只是更改了该数字所占用的内存量。这对于编译器做所需的一切都足够安全。

但是当你试图将字符串转换为数字时,你不能这样做,因为它不足以改变变量占用的内存量。例如, 5.00 作为字符串是一个“数字”序列:53(5)46(。)48(0)48(0) - 这是ASCII,但字符串将包含类似的东西。如果编译器只从字符串中取出第一个N(4表示双重?不确定)字节 - 该部分将包含完全不同的双数。 同时Convert.ToDouble()运行特殊算法,该算法将获取字符串的每个符号,找出它所代表的数字并为你创建一个双数,如果字符串代表一个数字。 大致来说,像PHP这样的语言会在后台为你调用Convert.ToDouble。但是C#就像一种静态类型的语言,不会为你做那件事。这使您可以确保任何操作都是类型安全的,并且您不会出现意外情况,例如:

double d = (double)"zzzz"

答案 7 :(得分:1)

不允许将字符串转换为双向字符串C#这就是为什么会出现异常,您需要转换字符串(MSDN doc显示可接受的转换路径)。这只是因为字符串不一定包含数字数据,但各种数字类型将(禁止空值)。 Convert将运行一个方法,该方法将检查字符串以查看它是否可以转换为数值。如果可以,那么它将返回该值。如果不能,则会抛出异常。

要进行转换,您有几种选择。您在问题中使用了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.
}

如果您尝试ConvertParse非数字字符串,这可以避免可能的异常。

答案 8 :(得分:1)

double varDouble = (double)variable假设variable已经是双倍。如果variable不是double(它是一个字符串),那么这将失败。 double varDouble = Convert.ToDouble(variable)就像它说的那样 - 它转换。如果它可以从variable解析或以其他方式提取双重,那么它就会。

我第二次使用Double.ParseDouble.TryParse,因为它更清楚地表明了应该发生的事情。你是从一个字符串开始,并希望它可以转换为双精度。如果有任何疑问,请使用TryParse

如果variable是方法参数,请将类型更改为double。让调用者负责提供正确的类型。这样编译器就能为你完成工作。

答案 9 :(得分:0)

最重要的区别是,如果使用类型转换并且转换失败 (例如,我们将一个非常大的float值转换为int),不会引发异常,并且将显示int可以容纳的最小值。 但是在使用转换的情况下,此类情况将引发异常。