一尘不染

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

c#

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


阅读 431

收藏
2020-05-19

共1个答案

一尘不染

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

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

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

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

这个主题非常广泛,因此让我们通过从游戏中排除自定义演员来缩小范围。

隐式转换

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

原始类型

例如:

int tinyInteger = 10;
long bigInteger = tinyInteger;

float tinyReal = 10.0f;
double bigReal = tinyReal;

这些转换是隐式的,因为在转换过程中,您不会丢失任何信息(只需将类型变大)。反之亦然,不允许进行隐式强制转换,因为无论其实际值如何(因为只能在运行时检查它们),在转换期间您可能会丢失一些信息。例如,此代码将不会编译,因为a
double可能包含(实际上包含)一个用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;

在这种情况下,编译器 知道
string工具IFormattableNotSupportedException是(派生自)Exception这样的转换是隐含的。因为对象不会改变其类型,所以不会丢失任何信息(这与structs和基本类型不同,因为使用强制类型转换您可以创建
另一个类型新对象 ),您对它们的 看法 有所改变。

明确的演员表

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

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

原始类型

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

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#中使用强制转换对编译器说:

相信我,即使您现在不知道这种类型,也要让我知道,您会看到的。

-要么-

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

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

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

double value = Double.Parse(aStringVariable);

当然,这可能会失败,因此,如果执行此操作,则应始终捕获它可能引发的异常(FormatException)。这是在这里的话题,但是,当如果TryParse是可用的,那么(因为语义你,你应该使用它
说, 它可能不是一个数字,它甚至更快......失败)。

.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
,即使可以使用强制转换进行相同的转换(除非有其他可用的方法)。 可以清楚地知道谁将读取您的代码,您的意图是什么 ,它可能会失败(简化调试)。

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

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

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

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

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

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

2020-05-19