19

我正在将 CSV 文件解析为具有强类型属性的对象列表。这涉及将文件中的每个字符串IConvertible值解析为使用. intdecimaldoubleDateTimeTypeDescriptor

我正在使用 atry catch来处理解析失败的情况。然后记录此异常发生的位置和原因的确切详细信息以供进一步调查。下面是实际的解析代码:

try
{
    parsedValue = TypeDescriptor.GetConverter(type).ConvertFromString(dataValue);
}
catch (Exception ex)
{
    // Log failure
}

问题:

成功解析值后,该过程很快。当解析包含大量无效数据的数据时,该过程可能会慢数千倍(由于捕获异常)。

我一直在通过解析到DateTime. 这些是性能数据:

  • 成功解析:平均每次解析32 个滴答声
  • 解析失败:每次解析平均146296 个滴答

这比它慢了 4500 倍。

问题:

我是否可以检查是否可以成功解析字符串值而无需使用昂贵的try catch方法?或者也许还有另一种方式我应该这样做?

编辑:我需要使用TypeDescriptor(而不是DateTime.TryParse),因为类型是在运行时确定的。

4

5 回答 5

13

如果您有一组已知的类型要转换,您可以执行一系列if/elseif/elseif/else(或switch/case在类型名称上)基本上将其分配给专门的解析方法。这应该很快。这如@Fabio's answer中所述。

如果您仍然有性能问题,您还可以创建一个查找表,让您根据需要添加新的解析方法来支持它们:

给定一些基本的解析包装器:

public delegate bool TryParseMethod<T>(string input, out T value);

public interface ITryParser
{
    bool TryParse(string input, out object value);
}

public class TryParser<T> : ITryParser
{
    private TryParseMethod<T> ParsingMethod;

    public TryParser(TryParseMethod<T> parsingMethod)
    {
        this.ParsingMethod = parsingMethod;
    }

    public bool TryParse(string input, out object value)
    {
        T parsedOutput;
        bool success = ParsingMethod(input, out parsedOutput);
        value = parsedOutput;
        return success;
    }
}

然后,您可以设置一个转换助手来进行查找并调用适当的解析器:

public static class DataConversion
{
    private static Dictionary<Type, ITryParser> Parsers;

    static DataConversion()
    {
        Parsers = new Dictionary<Type, ITryParser>();
        AddParser<DateTime>(DateTime.TryParse);
        AddParser<int>(Int32.TryParse);
        AddParser<double>(Double.TryParse);
        AddParser<decimal>(Decimal.TryParse);
        AddParser<string>((string input, out string value) => {value = input; return true;});
    }

    public static void AddParser<T>(TryParseMethod<T> parseMethod)
    {
        Parsers.Add(typeof(T), new TryParser<T>(parseMethod));
    }

    public static bool Convert<T>(string input, out T value)
    {
        object parseResult;
        bool success = Convert(typeof(T), input, out parseResult);
        if (success)
            value = (T)parseResult;
        else
            value = default(T);
        return success;
    }

    public static bool Convert(Type type, string input, out object value)
    {
        ITryParser parser;
        if (Parsers.TryGetValue(type, out parser))
            return parser.TryParse(input, out value);
        else
            throw new NotSupportedException(String.Format("The specified type \"{0}\" is not supported.", type.FullName));
    }
}

那么用法可能是这样的:

//for a known type at compile time
int value;
if (!DataConversion.Convert<int>("3", out value))
{
    //log failure
}

//or for unknown type at compile time:
object value;
if (!DataConversion.Convert(myType, dataValue, out value))
{
    //log failure
}

这可能会扩展泛型以避免object装箱和类型转换,但就目前而言,这很好用;如果您从中获得可衡量的性能,也许只有优化该方面。

编辑:您可以更新该DataConversion.Convert方法,以便如果它没有注册指定的转换器,它可以回退到您的TypeConverter方法或引发适当的异常。如果您想拥有一个包罗万象的内容,或者只是拥有预定义的支持类型集并避免try/catch重蹈覆辙,这取决于您。就目前而言,代码已更新为抛出一个NotSupportedException指示不受支持的类型的消息。随意调整,因为它是有道理的。性能方面,也许做一个包罗万象是有意义的,因为一旦你为最常用的类型指定了专门的解析器,这些可能会越来越少。

于 2013-05-30T12:50:16.563 回答
6

如果您知道要解析的类型,请使用 TryParse 方法:

String value;
Int32 parsedValue;
if (Int32.TryParse(value, parsedValue) == True)
    // actions if parsed ok
else
    // actions if not parsed

其他类型也一样

Decimal.TryParse(value, parsedValue)
Double.TryParse(value, parsedValue)
DateTime.TryParse(value, parsedValue)

或者您可以使用下一个解决方法:

为每个具有相同名称但签名不同的类型创建一个解析方法(在其中包装 TryParse):

Private bool TryParsing(String value, out Int32 parsedValue)
{
    Return Int32.TryParse(value, parsedValue)
}

Private bool TryParsing(String value, out Double parsedValue)
{
    Return Double.TryParse(value, parsedValue)
}

Private bool TryParsing(String value, out Decimal parsedValue)
{
    Return Decimal.TryParse(value, parsedValue)
}

Private bool TryParsing(String value, out DateTime parsedValue)
{
    Return DateTime.TryParse(value, parsedValue)
}

然后你可以使用TryParsing你的类型的方法

于 2013-05-30T12:15:59.457 回答
2

在调用 Parse 之前为每种类型构造一个正则表达式并将其应用于字符串怎么样?您必须构建正则表达式,以便如果字符串不匹配,则不会解析。如果字符串解析,这会慢一些,因为您必须进行正则表达式测试,但如果它不解析,它会更快。

您可以将正则表达式字符串放在 aDictionary<Type, string>中,这将使确定使用哪个正则表达式字符串变得简单。

于 2013-05-30T12:48:37.180 回答
2

您可以使用以下TryParse方法:

if (DateTime.TryParse(input, out dateTime))
{
    Console.WriteLine(dateTime);
}
于 2013-05-30T12:11:46.823 回答
1

这取决于。如果您使用的是 DateTime,则始终可以使用TryParse函数。这将快一个数量级。

于 2013-05-30T12:11:38.073 回答