TypeConverter与Convert与TargetType.Parse的区别

50
据我所知,.NET 中至少有三种方法可以转换数据类型:
使用 System.ComponentModel.TypeConverter
var conv = System.ComponentModel.TypeDescriptor.GetConverter(typeof(int));
var i1 = (int)conv.ConvertFrom("123");

使用System.Convert.ChangeType()

var i2 = (int) Convert.ChangeType("123", typeof (int));

使用目标类型的Parse/TryParse方法:

var i3 = int.Parse("123"); // or TryParse
有没有任何准则或经验法则来判断何时使用哪种方法在 .NET 基本数据类型之间进行转换(尤其是从字符串到其他数据类型)?

哦,不要忘记 System.ComponentModel.NullableConverterSystem.Convert 无法处理可空类型(将 string "123" 转换为类型 int?),而他们没有将其构建到现有的转换器中,而是又创建了一个新的 :) - CodingWithSpike
7
Convert.ChangeType使用int.Parse(),所以在这方面没有区别。TypeConverter使用反射,如果你关心性能最好避免使用。 - Hans Passant
1
@Hans:为什么不把它发布为答案呢?看起来是一条有价值的信息。 - M4N
7个回答

10

我会在这里发布六年后的内容,因为我认为这是一个好问题,而且我对现有的答案不满意。

静态Parse/TryParse方法只能用于将字符串转换为具有这些方法的类型。(当您期望转换可能失败时,请使用TryParse)。

System.Convert的作用是根据其文档,将数据从一种基本数据类型转换为另一种基本数据类型。请注意,使用Convert时,您还可以使用将对象作为参数的方法,并由它们自己确定如何进行转换。

至于System.ComponentModel.TypeConverter,作为 “typeconverter” stack overflow标签的文档所述,它们主要用于在希望提供类实例的文本表示以供设计器序列化或在属性网格中显示时,将数据转换为和从字符串转换


6

转换

Convert类使用目标类型中实现的IConvertible方法。

不幸的是,实现 IConvertible 意味着编写大量样板代码,Convert.ChangeType 会在目标类型为结构时造成装箱。

TypeConverterAttribute

TypeDescriptor.GetConverter 使用TypeConverterAttribute并提供更好的API来转换类型以及更优雅的方式来使类型可转换。但由于方法不是通用的,它与 Convert 类一样存在性能问题。

Parse/TryParse

使用 T.Parse / T.TryParse 方法是从字符串创建对象的事实标准,因为它不涉及不必要的装箱。它们通常有多个重载,提供更多控制如何解析字符串的选项。

TryParse 方法允许您处理从用户输入或其他方式获得的、无法保证格式正确的字符串,而不会抛出异常。


因此,在编译时知道目标类型时,应调用类型的 Parse/TryParse 方法(如果可以),只有在不知道目标类型时才回退到其他方式,例如当您只有表示目标类型的Type对象时。

您还可以查看我的小库,称为ValueString,它找到类型最适合的解析方法并使用它来解析字符串。


4

根据我的个人偏好和编程标准,我在以下选项之间进行选择:

  1. Convert. I use this when I am absolutely sure that the values will be what I expect.

    int i = Convert.ToInt32("123");
    
  2. TryParse. I use this when I am handling user input. This also has the benefit to be able to use localized formatting when parsing.

    int i = 0;
    bool parsed = Int32.TryParse("123", out i);
    

还有一种可能性是使用TryParseExact,可以解析某个特定的模式。在某些情况下非常有用。


3
作为一个经验法则,您应该尽量避免使用Convert类。有一些情况需要使用它(例如,您不知道源类型),但如果您已经知道源类型是string,那么使用Parse方法(或更正确地说,TryParse)总是正确的方法。
至于类型转换器,它们往往在框架(如WPF)使用反射来确定正确的类型转换器时使用。
您忘记了另一种转换方式,即直接强制转换。以这段代码为例:
object i = 1;
int myInt = (int)i;

这是一个有点牵强的例子,但我已经知道 i 是一个整形变量,只是被装箱成了一个 object 对象。在这种情况下,我不需要转换 i,只需要将其直接转型为我已知的类型即可。


1
@Jamiec,你能否解释一下你的语句“通常情况下,应该避免使用Convert类”的原因?我想看看一些论据、博客文章等来支持这个说法。谢谢。 - Dave Black
@DaveBlack - 或者,你可以去做你自己的研究?坦率地说,“转换”(Convert)就像是一把大锤子,而通常你需要的是一把小锤子(比喻意义上,例如 int.Parse)。看看反编译源代码中的 Convert.ChangeType,看看它尝试了多少方法。往往你会直接知道应该使用哪个转换方法,而不是采用“一刀切”的方式。 - Jamiec
1
我已经做了研究,没有发现任何引起警惕的问题(这就是为什么我问你避免使用它的强烈意见的原因)。通过使用 Vance Morrison 的 MeasureIt 应用程序(前 CLR 性能架构师),性能在微秒级别上足够接近,没有任何实质性的差异。如果类存在,并且没有明智的理由不使用它,那么我会建议使用它。我完全支持了解框架和运行时的知识。然而,在这种情况下,查看 Convert.ChangeType() 只是一种学术练习而已。 - Dave Black
1
继续上文,有一些关于拥有一个框架只为你做事并抽象实现细节的说法。与普遍观点相反,在.NET 4.5中使用类型系统非常快,因为反射引擎基本上被重新编写了。我已经在其周围进行了大量性能测试,它比框架先前版本中的速度快了几个数量级。所以,如果一个“一刀切”的解决方案既执行效果良好又更易于维护,那么有什么问题呢?而不是在应用程序中分散多种类型转换的方式。 - Dave Black
续上:3. 它同样有效。我应该补充说明的是,所有性能测试都是使用 MeasureIt 进行的,而不是大多数人用于性能测量的典型(且常常不准确的)Linqpad / Stopwatch 测试。 - Dave Black
显示剩余2条评论

3

刚刚发现如果你想将整数0/1转换为布尔型,使用TypeConvert.ConvertFrom(1) 或者 (0) 会抛出异常。在这种情况下,Convert.ChangeType(1, System.Boolean) 是可行的。


同样地,无论值如何,TypeConvert.ConvertFrom() 将 Int64 转换为 Int32 时都会抛出异常,而 Convert.ChangeType() 只要值不溢出就可以成功转换。 - Ian Goldby

1

当我确定它是一个数字时,我几乎总是使用int/double等.Parse()方法。在任何怀疑的情况下,我使用.TryParse()方法,作为一种包括解析和检查的全能解决方案。我有这样一种感觉,检查和解析结合起来比分开执行略微更有效率。

TypeConverter可能只在编译时不真正知道类型时有用。


0

又是一个晚回答。我有一个应用程序,其中我使用了一些类似于以下代码的代码:

var finalValue = Convert.ChangeType(sourceValue, targetProperty.PropertyType);

sourceValue将是我想要的string表示形式,通常会是wrapper class下的primitive。这种方式适用于常规数据类型,甚至包括整个类。然而,有一天我愚蠢地决定将一个属性的数据类型从double更改为可空的double?,猜猜发生了什么?炸了!

从字符串到可空double的无效转换

这是您应该使用TypeConverter 类的完美场景,如下所示:

void Main()
{
    var ss = new[] {"2.01", "3.99", ""};
    var conv = TypeDescriptor.GetConverter(typeof(A).GetProperties().First().PropertyType);
    Console.WriteLine(string.Join(",", ss.Select(s => conv.ConvertFrom(s))));
}

class A {
    public double? b {get; set;}
}

//2.01,3.99,

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接