【问题标题】:Difference between casting and using the Convert.To() method转换和使用 Convert.To() 方法之间的区别
【发布时间】:2013-03-13 18:55:28
【问题描述】:

我有一个函数可以将 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() 不会?

【问题讨论】:

  • 关于referenced question,OP 询问何时使用强制转换或转换,accepted answer 声明:“无论您使用哪种方式,这都是一个选择问题。”我要求区分演员表与转换者之间的区别。在我看来,下面的答案(赞!)提供了有关差异与“选择使用这个或那个”的更多细节......本质上,这个细节可以用来做出更明智的选择。
  • @edmastermind29 在编程上下文中“x 和 y 有什么区别”和“何时使用 x 和 y”之间没有太大区别。双方互相回答对方。
  • 将近 3 年后,在这种情况下,似乎没有一个相互回答另一个。问:“X 和 Y 有什么区别?” A:“这真的是一个选择的问题,无论你使用哪个。”不是很有用。
  • 似乎没有人能直接回答哪个表现最好也是问题的一部分,根据我的经验,我认为 Cast 更好,尤其是在获取这样的列值时.. (int)datatable.Rows [0][0],如果我们知道它的 100% int

标签: c# casting


【解决方案1】:

即使您可能将它们视为等价物,它们的用途也完全不同。让我们首先尝试定义什么是演员表:

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

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

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

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

隐式转换

在 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 实现了 IFormattable 并且 NotSupportedException 是(派生自)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# 中你对编译器说:

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

-或-

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

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

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

double value = Double.Parse(aStringVariable);

当然,这可能会失败,所以如果你这样做,你应该始终捕获它可能抛出的异常 (FormatException)。这里没有主题,但是当TryParse 可用时,您应该使用它(因为从语义上您它可能不是一个数字,而且它甚至更快......失败)。

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

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

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 数组与字符串之间的转换。

结论

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 案例案例!

【讨论】:

  • 我把问题改成了Difference between casting and using the Convert.To() method。否则,非常全面的答案。 (我希望我的问题被重新提出......)
  • @edmastermind29 我编辑了一点问题,主题太长,即使是很长的答案(300 多个可能的转换列表)。 Convert 不仅与强制转换相比,而且与“普通”的 IConvertible 和 IFormattable 接口相比,都增加了好处(或只是意外的行为?)。
  • 我不喜欢从 C 中借用的概念,即不代表整数的 double 值应该“可转换”为 int。在例如以下情况下,演员表似乎是合适的范例。一个是从double[] 中检索Int32 值,该double[] 包含实数和已转换为doubleInt32 值[试图转换一个无法精确表示的值 i> 在int32 中将表示意外情况并应触发异常],但我认为当人们想要有损转换时,应该具体说明想要的形式。
  • 另一个区别是对象类型和原始类型。例如object o = 123; var l = Convert.ToInt64(o); var i = (long) (int) o; var f = (long) o // InvalidCastException
  • @rory.ap 这很重要。不,正式不是强制转换 (float -> int),而是强制。例如,演员表可能是 DerivedClass -> BaseClass。这很令人困惑,因为在 C# 中我们对两者使用相同的词(和运算符),但它们实际上是不同的东西。区分它们的正式定义比我写的要复杂一些。
【解决方案2】:

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

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

那么,你什么时候转换,什么时候施放?在这两种情况下,我们都有一个类型的变量,比如说 A,我们想要一个 B 类型的变量。如果我们的 A 对象在底层确实是一个 B,那么我们进行转换。如果它不是真正的 B,那么我们需要转换它,并定义程序应该如何从 A 中获取 B。

【讨论】:

  • SO post Eric Lippert 的其中一个中提到,没有所谓的隐式转换,它是隐式转换。我一直在交替使用强制转换和转换。说“隐式演员”有什么问题?如果转换是隐式的而不需要任何强制转换,难道不是可以说它是“隐式强制转换”吗?
  • @RahulAgarwal 什么是强制转换是一种操作,您需要在其中明确指示给定类型是(或可以制成)另一种类型的有效实例。当存在隐式转换时,不需要强制转换将该类型视为另一种类型。所以说“隐式转换”并没有真正的意义(除了像 Eric 提到的在没有开发人员输入的情况下添加转换运算符的少数情况,比如使用 foreach 时)。除了这些例外,类型转换根据定义是显式的。
【解决方案3】:

来自MSDN

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

考虑以下示例:

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

还有:

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

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


结论: 在强制转换时,您告诉编译器 a 确实是 b 类型,如果是这样,项目构建时不会出现任何错误,如下例所示:
double s = 2;
int a = (int) s;

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

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

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

但是这个编译成功了:

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

但在运行时你会得到InvalidCastException,上面写着:

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

【讨论】:

    【解决方案4】:

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

    需要显式转换才能工作。

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

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

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

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

    访问MSDN Page 了解转换/转换规则

    【讨论】:

    • @edmastermind29 我已经更新了我的答案。我希望它能回答你的问题。
    • 与我的问题相关的显式演员表有哪些要求?是否与“非积分”值有关?
    • @edmastermind29 是的。如果您尝试转换为数字类型的值是非数字的,则转换无效.. 需要转换。
    【解决方案5】:

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

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

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

    【讨论】:

    • 这与显式转换有何不同?
    • 显式转换不看数据类型是什么,它只看字节。例如,将 char x = '1' 转换为整数,整数将为 49,因为字符 '1' 在 ascii 表中是 #49
    • @user1751547 那么,Convert.ToDouble() 的用户是否会超越字节并考虑数据?
    • @user1751547 我认为这是正确回答这个问题所需的直觉。简单地说“它没有定义”有点没有实际意义。
    • @edmastermind29 是的,它会查看输入类型,如果是字符串,它将遍历每个字符,知道如果 char 的 ascii 值为 49,那么它就是 '1'字符并正确转换
    【解决方案6】:

    铸造不涉及任何转换,即不改变值的内部表示。示例:

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

    【讨论】:

    • Convert.ToDouble() internally calls Double.Parse()。使用Convert.ToDouble() 而不是Double.Parse() 对我有利吗?为什么?
    • Convert.ToDouble 有很多接受不同类型输入的重载。如果传递了null 字符串,则接受string 的重载将返回0.0。除此之外,我认为使用它没有任何优势。
    • 那么,或者...或者Double.Parse() 有什么值得我考虑的吗?
    • Double.Parse()Convert.ToDouble() 更直接。如果您确定您的字符串将包含有效数字,则可以放心使用它,否则我建议您使用Double.TryParse
    【解决方案7】:

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

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

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

    【讨论】:

      【解决方案8】:
      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"
      

      【讨论】:

      • @edmastermind29 查看我的更新答案。我试图解释它。解释远非完美,但假设它解释了差异。
      【解决方案9】:

      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 非数字字符串,这可以避免可能出现的异常。

      【讨论】:

      • 使用TryParse 而不是Convert 对我有利吗,因为TryParse 会检查转换是否成功?
      • @edmastermind29 我想是的。如果转换失败,Convert 将抛出异常。 TryParse 将返回一个布尔值,如果转换成功,则返回 True,如果失败,则返回 False。
      【解决方案10】:

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

      【讨论】:

        猜你喜欢
        • 2010-10-08
        • 1970-01-01
        • 2021-07-15
        • 1970-01-01
        • 1970-01-01
        • 2011-05-19
        • 2013-05-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多