106

我有一个doublestring值进行强制转换的函数。

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()不?

4

10 回答 10

142

即使您可能以某种方式将它们视为等效,它们的目的也完全不同。让我们首先尝试定义什么是演员表:

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

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

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

这个话题非常广泛,所以让我们尝试通过从游戏中排除自定义演员操作员来缩小范围。

隐式强制转换

在 C# 中,当您不会丢失任何信息时,强制转换是隐式的(请注意,此检查是使用类型而不是它们的实际值执行的)。

原始类型

例如:

int tinyInteger = 10;
long bigInteger = tinyInteger;

float tinyReal = 10.0f;
double bigReal = tinyReal;

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

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

对象

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

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

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

在这种情况下,编译器知道实现并且是(派生自)所以强制转换是隐式的。不会丢失任何信息,因为对象不会更改它们的类型(这与s 和原始类型不同,因为通过强制转换可以创建另一种类型的新对象),改变的是您对它们的看法stringIFormattableNotSupportedExceptionExceptionstruct

显式强制转换

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

  • 您可能会丢失信息或数据,因此您必须注意这一点。
  • 转换可能会失败(因为您无法将一种类型转换为另一种),因此,您必须再次注意自己在做什么。

原始类型

在转换期间您可能会丢失一些数据时,原始类型需要显式转换,例如:

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?忽略来自Convertimplementation 和IConvertibleimplementations 的细微差异实际上是因为在 C# 中你对编译器说:

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

-或者-

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

对于其他任何事情,都需要明确的操作(想想简单强制转换的含义,这就是 C++ 为它们引入冗长、冗长和明确的语法的原因)。这可能涉及复杂的操作(对于string->double转换,需要解析)。string例如,转换为 始终是可能的(通过ToString()方法),但它可能意味着与您期望的不同,因此它必须比强制转换更明确(您写得越多,您就越想自己在做什么)。

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

double value = Double.Parse(aStringVariable);

当然这可能会失败,所以如果你这样做,你应该总是捕捉它可能抛出的异常(FormatException)。这不在主题范围内,但是当 aTryParse可用时,您应该使用它(因为从语义上讲,可能不是数字,而且它甚至更快……失败)。

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

为了完成这个冗长的答案,只需简单介绍一下用户定义的转换运算符。让程序员使用强制转换将一种类型转换为另一种类型只是糖。这是一个类(将被强制转换的)中的一个方法,它说“嘿,如果他/她想将这种类型转换为那种类型,那么我可以做到”。例如:

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

在您的实现中,您可能决定“让程序员的生活更轻松”并通过强制转换公开这种转换(请记住,这只是少写的捷径)。某些语言甚至可能允许这样做:

double value = "123";

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

我能用它们做什么?

所以最后一个问题是你什么时候应该使用一个或另一个。让我们看看何时可以使用显式强制转换:

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

只有第一次转换可以完成,Convert所以对于其他人你别无选择,你需要使用显式转换。

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

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

结论

Convert每次您知道转换可能失败(因为格式、范围或可能不受支持)时,都应使用IMO ,即使可以使用强制转换完成相同的转换(除非有其他可用的东西)。它明确了谁将阅读您的代码您的意图是什么,并且它可能会失败(简化调试)。

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

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

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

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

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

很不一样,对吧?强制转换会截断(这是我们都期望的),但Convert会四舍五入到最接近的整数(如果您不知道,这可能不会发生)。每种转换方法都会引入差异,因此不能应用一般规则,必须逐个查看它们...19 种基本类型可以转换为其他所有类型...列表可能很长,最好参考 MSDN 案例案子!

于 2013-03-13T20:29:08.327 回答
13

Casting is a way of telling the compiler, "I know that you think that this variable is a Bar, but I happen to know more than you; the object is actually a Foo, so let me treat it as if it were a Foo from now on." Then, at runtime, if the actual object turned out to really be a Foo then your code works, if it turns out that the object was not a Foo at all, then you get an exception. (Specifically an System.InvalidCastException.)

Converting on the other hand is a way of saying, "If you give me an object of type Bar I can create a brand new Foo object that represents what is in that Bar object. I won't change the original object, it won't treat the original object differently, it will create something new that is just based on some other value. As to how it will do that, it could be anything. In the case of Convert.ToDouble it will end up calling Double.Parse which has all sorts of complex logic for determining what types of strings represent what numeric values. You could write your own conversion method that mapped strings to doubles differently (perhaps to support some entirely different convention for displaying numbers, such as roman numerals or whatever). A conversion could do anything, but the idea is that you're not really asking the compiler to do anything for you; you are the one writing the code to determine how to create the new object because the compiler, without your help, has no way of knowing how to map (as an example) a string to a double.

So, when do you convert, and when do you cast? In both cases we have some variable of a type, let's say A, and we want to have a variable of type B. If our A object really, actually, under the hood, is a B, then we cast. If it's not really a B, then we need to Convert it, and define how the program is supposed to get a B from an A.

于 2013-03-13T19:21:37.057 回答
6

来自MSDN

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

考虑以下示例:

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

并且:

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

System.Convert当您想在不兼容的类型之间进行转换时,您可以使用类。castconvert之间的主要区别compilerun-time。类型转换异常出现在运行时,即在运行时失败的类型转换将导致抛出一个。 InvalidCastException


结论:在强制转换时,您告诉编译器a真正的类型b,如果是这样,则项目构建时不会出现任何错误,例如以下示例:

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

但是在转换中你对编译器说有一种方法可以从atype创建一个新对象b,请这样做并且项目构建没有任何错误,但正如我所说,如果类型转换在运行时失败,它将InvalidCastException导致被抛出

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

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

但是这个编译成功:

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

但是在运行时你会得到InvalidCastException这样的信息:

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

于 2016-03-05T07:16:47.453 回答
4

在您的示例中,您试图将字符串转换为双精度(非整数类型)。

它需要显式转换才能工作。

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

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

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

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

访问此MSDN 页面以了解强制转换/转换的规则

于 2013-03-13T19:01:51.000 回答
4

Casting does not involve any conversion, i.e. the internal representation of a value is not changed. Example:

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.

You can convert a double to string like this

double d = 5;
string s = d.ToString(); // -> "5"

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

You can convert a string to a double in several ways (here just two of them):

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

Or the safe way

string s = "5";
double d;
if (Double.TryParse(s, out d)) {
    Console.WriteLine("OK. Result = {0}", d);
} else {
    Console.WriteLine("oops!");
}
于 2013-03-13T19:14:55.290 回答
4

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

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

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

于 2013-03-13T18:58:20.427 回答
2

double varDouble = (double)variable assumes that variable is already a double. If variable isn't a double (it's a string) then this will fail. double varDouble = Convert.ToDouble(variable) does like it says - it converts. If it can parse or otherwise extract a double from variable then it will.

I second using Double.Parse or Double.TryParse because it more clearly indicates what's supposed to be happening. You're starting with a string and expecting it to be convertible to a double. If there's any doubt, use TryParse.

If variable is a method argument, change the type to double. Make the caller responsible for providing the correct type. That way the compiler does the work for you.

于 2016-03-05T03:36:15.280 回答
1

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

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

如果您尝试使用非数字字符串,这可以避免可能出现的Convert异常Parse

于 2013-03-13T19:03:45.317 回答
1
string variable = "5.00";     
double varDouble = (double)variable;

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

有关在此处投射的更多信息

这与 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"
于 2013-03-13T19:02:18.903 回答
-1

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

于 2019-01-05T21:03:26.107 回答