即使您可能以某种方式将它们视为等效,它们的目的也完全不同。让我们首先尝试定义什么是演员表:
转换是将一种数据类型的实体更改为另一种数据类型的操作。
它有点通用,并且在某种程度上等同于转换,因为转换通常具有与转换相同的语法,所以问题应该是语言何时允许转换(隐式或显式)以及何时必须使用 (更多)显式转换?
让我先在它们之间画一条简单的线。形式上(即使与语言语法等效)强制转换将更改类型,而转换将/可能更改值(最终与类型一起)。演员表也是可逆的,而转换可能不可逆。
这个话题非常广泛,所以让我们尝试通过从游戏中排除自定义演员操作员来缩小范围。
隐式强制转换
在 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 和原始类型不同,因为通过强制转换可以创建另一种类型的新对象),改变的是您对它们的看法。string
IFormattable
NotSupportedException
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
implementation 和IConvertible
implementations 的细微差异实际上是因为在 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 ,即使可以使用强制转换完成相同的转换(除非有其他可用的东西)。它明确了谁将阅读您的代码您的意图是什么,并且它可能会失败(简化调试)。
对于您需要使用演员表的其他一切,别无选择,但如果有另一种更好的方法可用,那么我建议您使用它。在您的示例中,从string
to的转换double
通常会失败(尤其是如果文本来自用户),因此您应该尽可能明确地进行转换(此外,您可以对其进行更多控制),例如使用TryParse
方法。
编辑:它们之间有什么区别?
根据更新的问题并保留我之前写的内容(关于何时可以使用强制转换与何时可以/必须使用Convert
),最后要澄清的一点是它们之间是否存在差异(此外还Convert
使用IConvertible
和IFormattable
接口,以便它可以执行操作不允许使用演员表)。
简短的回答是肯定的,它们的行为不同。我看到这个Convert
类就像一个辅助方法类,所以它经常提供一些好处或稍微不同的行为。例如:
double real = 1.6;
int castedInteger = (int)real; // 1
int convertedInteger = Convert.ToInt32(real); // 2
很不一样,对吧?强制转换会截断(这是我们都期望的),但Convert
会四舍五入到最接近的整数(如果您不知道,这可能不会发生)。每种转换方法都会引入差异,因此不能应用一般规则,必须逐个查看它们...19 种基本类型可以转换为其他所有类型...列表可能很长,最好参考 MSDN 案例案子!