2013-05-30 102 views
18

我正在將CSV文件解析爲具有強類型屬性的對象列表。這包括使用TypeDescriptor將文件中的每個字符串值解析爲IConvertible類型(int,decimal,double,DateTime等)。檢查是否可以解析字符串的最快方法

我正在使用try catch來處理解析失敗時的情況。然後記錄下發生此異常的具體細節,以便進一步調查。下面是實際解析代碼:

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

問題:

當值被成功解析,這個過程是快速的。在分析包含大量無效數據的數據時,該進程可能會花費數千次(由於捕獲異常)。

我一直在測試這與分析DateTime。這是表現人物:

  • 成功解析:平均32蜱每解析
  • 無法解析:平均146296蜱每解析

這是慢超過4500倍。

問:

是否有可能爲我檢查,看看一個字符串值,而無需使用昂貴的我的方法try catch成功解析?或者也許有另一種方式我應該這樣做?

編輯:我需要使用TypeDescriptor(而不是DateTime.TryParse),因爲該類型在運行時確定。

+0

如果您知道'type',您可以嘗試手動觸碰每個人的各種'TryParse'方法並查看是否有幫助。 –

+1

您是否試圖敲擊每個類型的解析器,以查看哪一個與CSV條目兼容?也就是說,首先嚐試'DateTime',然後嘗試'int',然後嘗試'decimal',然後嘗試'double',然後將所有內容轉換爲'string'?或者你知道某個條目應該是'DateTime',但有時/通常數據本身的格式不正確? –

+0

另外,你是在'release'模式還是'debug'模式下執行這個基準測試,還是附加了調試器?如果在「調試」模式下,它可能會出於調試目的報告/存儲過多的異常/堆棧信息。 –

回答

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方法或拋出適當的異常。如果你想擁有一個全面的或者只是有你預定義的支持類型,並且避免再次使用你的產品,這取決於你。就目前而言,代碼已更新爲拋出NotSupportedException並顯示一條消息,指示不支持的類型。隨意調整,因爲它是有道理的。在性能方面,也許這樣做是有道理的,因爲一旦你爲最常用的類型指定了專門的解析器,那麼可能會更少和更多。

+2

好,但是你應該使用'Parsers.TryGetValue'並且回退到'TypeConverter'和異常處理的意想不到的類型。 –

+1

@BenVoigt有趣的是,我只是補充說。我開始做'TypeConverter'方法,但我認爲這會忽略這一點。 davenewza指出他有一個固定數量的類型,並且首先想避免'try/catch'異常處理。 –

+0

傑出的解決方案克里斯。我完全忘了我也需要迎合'Nullable'類型。可能你的解決方案可能是......可空的'也許?我現在會自己嘗試這個。 – davenewza

1

這取決於。如果您使用DateTime,則始終可以使用TryParse函數。這將會更快。

+0

我需要能夠解析任何類型,因此我需要使用TypeDescriptor。編輯過的文章! – davenewza

+0

您可以對已知類型使用TryParse方法,併爲其他類型調用TypeDescritor。 –

2

你可以使用TryParse方法:

if (DateTime.TryParse(input, out dateTime)) 
{ 
    Console.WriteLine(dateTime); 
} 
+0

我需要能夠解析任何類型,因此我需要使用TypeDescriptor。編輯過的文章! – davenewza

+0

如果你隱藏你的對象首先與toString()字符串比,並使用該:),保持我們發佈有關您的發現,所以我們可以學習最快的方式來解析:) – Apocalyp5e

4

如果你知道你試圖解析一個類型,然後使用的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與類型

+1

所以,你會建議我進行類型檢查,然後運行相關的TryParse方法? – davenewza

+1

@davenewza是的,如果你有一個固定數量的支持類型。如果有必要的話,你可以移動「類型檢查」來表示由「Type」鍵入的查找表,它的值是某種解析器或委託。 –

+0

每個唯一類型只有一個'Type'對象,因此您可以將它們與'ReferenceEquals'進行比較,該ReferenceEquals比較對象引用,與捕獲異常相比,這應該非常快。 –

2

如何構造正則表達式對於每種類型並在調用Parse之前將其應用於字符串?你必須建立正則表達式,如果字符串不匹配,它不會解析。如果字符串解析得很慢,因爲你必須做正則表達式測試,但是如果它不解析,速度會更快。

你可以把正則表達式的字符串的Dictionary<Type, string>,這將使確定要使用的正則表達式字符串簡單。

+0

這是一個好主意。唯一的問題是這需要文化敏感。所以,根據不同的文化背景,它會有不同的解析。例如,小數點可以是點或逗號。 – davenewza

+0

然後你必須編寫邏輯來使用當前的文化設置來構建正則表達式字符串。你需要的一切都在那個對象中。至少這是你在初始化時只需要做的事情,也許如果文化設置改變了。 –

+0

*你必須建立正則表達式,如果字符串不匹配,它不會解析。*這比聽起來要困難得多。解析* valid *'byte'值的正則表達式出人意料地涉及到了。如果你堅持使用正則表達式,你最好用它們來預先過濾顯然不好的東西,但仍然處理可能出現在「解析」中的異常。 –

相關問題