2013-05-30 36 views
18

Tôi đang phân tích tệp CSV thành danh sách đối tượng có thuộc tính được nhập mạnh không. Điều này liên quan đến việc phân tích từng giá trị chuỗi từ tệp thành loại IConvertible (int, decimal, double, DateTime, v.v ... sử dụng TypeDescriptor.Cách nhanh nhất để kiểm tra xem chuỗi có thể được phân tích cú pháp

Tôi đang sử dụng try catch để xử lý các tình huống khi phân tích cú pháp không thành công. Các chi tiết chính xác về nơi và tại sao ngoại lệ này xảy ra sau đó được ghi lại để điều tra thêm. Dưới đây là mã thực sự phân tích:

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

Vấn đề:

Khi giá trị được phân tách thành công, quá trình này là nhanh chóng. Khi phân tích cú pháp dữ liệu với nhiều dữ liệu không hợp lệ, quá trình có thể mất hàng nghìn lần chậm hơn (do bắt ngoại lệ).

Tôi đã thử nghiệm điều này với phân tích cú pháp thành DateTime. Đây là những con số thực hiện:

  • thành công phân tích cú pháp: bình quân 32 ve mỗi phân tích cú pháp
  • Không phân tích: trung bình 146.296 ve mỗi phân tích cú pháp

Đó là chậm hơn 4500 lần.

Câu hỏi:

Có thể cho tôi để kiểm tra xem một giá trị chuỗi có thể được phân tích thành công mà không cần phải sử dụng đắt try catch phương pháp của tôi? Hoặc có lẽ có một cách khác tôi nên làm điều này?

CHỈNH SỬA: Tôi cần sử dụng TypeDescriptor (và không phải DateTime.TryParse) vì loại được xác định khi chạy.

+0

Nếu bạn biết 'loại', bạn _could_ thử theo cách thủ công các phương thức' TryParse' khác nhau của mỗi người và xem điều đó có hữu ích không. –

+1

Bạn có đang cố gắng nhấn từng trình phân tích cú pháp loại để xem trình phân tích cú pháp nào tương thích với mục nhập CSV không? Tức là, trước tiên bạn thử 'DateTime', sau đó bạn thử' int', sau đó bạn thử 'decimal', sau đó bạn thử' double', sau đó bắt tất cả thành 'string'? Hoặc bạn có _know_ rằng một mục nào đó _should_ là 'DateTime', nhưng đôi khi/thường dữ liệu có định dạng không chính xác? –

+0

Ngoài ra, bạn có đang thực hiện điểm chuẩn này trong chế độ 'release' hoặc chế độ' debug', hoặc với trình gỡ lỗi đính kèm không? Nếu ở chế độ 'debug', nó có thể đang báo cáo/lưu trữ thông tin ngoại lệ/ngăn xếp quá mức cho các mục đích gỡ lỗi. –

Trả lời

13

Nếu bạn có một bộ loại được xác định để chuyển đổi, bạn có thể thực hiện một loạt if/elseif/elseif/else (hoặc switch/case trên tên loại) để phân phối về phương pháp phân tích chuyên biệt. Điều này sẽ khá nhanh. Điều này được mô tả trong @Fabio's answer.

Nếu bạn vẫn còn có vấn đề hiệu suất, bạn cũng có thể tạo ra một bảng tra cứu sẽ cho phép bạn thêm các phương pháp phân tích mới như bạn cần phải hỗ trợ họ:

Với một số giấy gói phân tích cơ bản:

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; 
    } 
} 

Sau đó, bạn có thể thiết lập trình trợ giúp chuyển đổi thực hiện tra cứu và gọi trình phân tích cú pháp thích hợp:

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

Sau đó sử dụng có thể như sau:

//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 
} 

Điều này có thể có các generics được mở rộng để tránh object đấm bốc và loại đúc, nhưng khi nó đứng này hoạt động tốt; có lẽ chỉ tối ưu hóa khía cạnh đó nếu bạn có một hiệu suất đo lường được từ nó.

EDIT: Bạn có thể cập nhật phương thức DataConversion.Convert để nếu không có công cụ chuyển đổi được chỉ định đã đăng ký, nó có thể quay trở lại phương thức TypeConverter hoặc ném ngoại lệ thích hợp. Tùy thuộc vào bạn nếu bạn muốn nắm bắt tất cả hoặc chỉ đơn giản là có các loại được hỗ trợ được xác định trước và tránh bị try/catch lặp lại. Khi nó đứng, mã đã được cập nhật để ném một NotSupportedException với một thông báo cho biết loại không được hỗ trợ. Vui lòng tinh chỉnh vì điều đó có ý nghĩa. Hiệu suất khôn ngoan, có lẽ nó làm cho tinh thần để làm catch-tất cả như có lẽ những người sẽ ít hơn và xa giữa một khi bạn chỉ định chuyên ngành phân tích cú pháp cho các loại phổ biến nhất được sử dụng.

+2

Tốt, nhưng bạn nên sử dụng' Parsers.TryGetValue' và dự phòng cho 'TypeConverter' và xử lý ngoại lệ cho các loại unanticipated. –

+1

@BenVoigt Vui, tôi đã _just_ thêm điều đó. Tôi bắt đầu thực hiện phương thức 'TypeConverter' nhưng tôi nghĩ rằng điều đó sẽ thiếu điểm này. davenewza đã xác định rằng anh ta có một số kiểu cố định và muốn tránh xử lý ngoại lệ 'try/catch' ngay từ đầu. –

+0

Giải pháp tuyệt vời Chris. Tôi hoàn toàn quên rằng tôi cũng cần phải phục vụ cho các loại 'Nullable'. Nó sẽ có thể với giải pháp của bạn ... 'Nullable ' có lẽ? Tôi sẽ cố gắng này bây giờ bản thân mình. – davenewza

1

Điều đó tùy thuộc. Nếu bạn đang sử dụng DateTime, bạn luôn có thể sử dụng chức năng TryParse. Đây sẽ là một cường độ nhanh hơn.

+0

Tôi cần có khả năng phân tích cú pháp thành bất kỳ loại nào, vì vậy tại sao tôi cần sử dụng TypeDescriptor. Đã chỉnh sửa bài đăng! – davenewza

+0

bạn có thể sử dụng phương pháp TryParse cho các loại đã biết và gọi TypeDescritor cho các loại khác. –

2

Bạn có thể sử dụng TryParse phương pháp:

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

Tôi cần có khả năng phân tích cú pháp thành bất kỳ loại nào, vì vậy tại sao tôi cần sử dụng TypeDescriptor. Đã chỉnh sửa bài đăng! – davenewza

+0

nếu bạn covert đối tượng của bạn thành chuỗi đầu tiên với toString() và sử dụng :), Hãy để chúng tôi đăng về những phát hiện của bạn để chúng tôi có thể tìm hiểu cách nhanh nhất để phân tích cú pháp :) – Apocalyp5e

4

Nếu bạn biết một loại nơi bạn cố gắng để phân tích, sau đó sử dụng phương pháp TryParse:

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

Cùng với nhiều loại khác

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

Hoặc bạn có thể sử dụng giải pháp thay thế tiếp theo:

Tạo một phương pháp phân tích cú pháp cho tất cả các loại cùng tên, nhưng chữ ký khác nhau (quấn TryParse bên trong của họ):

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) 
} 

Sau đó, bạn có thể sử dụng phương pháp TryParsing với các loại bạn

+1

Vì vậy, bạn sẽ khuyên tôi nên thực hiện kiểm tra kiểu và sau đó chạy phương thức 'TryParse' có liên quan? – davenewza

+1

@davenewza Có, nếu bạn có một số loại cố định được hỗ trợ. Nếu cần, bạn có thể di chuyển "kiểm tra kiểu" để nói bảng tra cứu được khóa bằng 'Loại' và giá trị của nó là một số loại trình phân tích cú pháp hoặc đại biểu. –

+0

Chỉ có một đối tượng 'Loại' cho từng loại duy nhất, do đó bạn có thể so sánh chúng với' ReferenceEquals', so sánh các tham chiếu đối tượng, sẽ rất nhanh so với ngoại lệ bắt. –

2

Làm thế nào về việc xây dựng một biểu thức chính quy cho mỗi loại và áp dụng nó cho chuỗi trước khi gọi Parse? Bạn sẽ phải xây dựng biểu thức chính quy như vậy nếu chuỗi không khớp, nó sẽ không phân tích cú pháp. Điều này sẽ chậm hơn một chút nếu chuỗi phân tích cú pháp vì bạn phải thực hiện kiểm tra regex, nhưng sẽ nhanh hơn nếu nó không phân tích cú pháp.

Bạn có thể đặt các chuỗi regex trong một Dictionary<Type, string>, điều này sẽ xác định chuỗi regex nào để sử dụng đơn giản.

+0

Đó là một ý tưởng tốt. Vấn đề duy nhất là điều này cần phải nhạy cảm với văn hóa. Vì vậy, tùy thuộc vào các thiết lập văn hóa, nó sẽ phân tích khác nhau. Ví dụ, các điểm thập phân có thể là một điểm hoặc dấu phẩy. – davenewza

+0

Sau đó, bạn sẽ phải viết logic để xây dựng các chuỗi regex bằng cách sử dụng các thiết lập văn hóa hiện tại. Mọi thứ bạn cần đều nằm trong đối tượng đó. Ít nhất đó là điều bạn chỉ phải làm lúc khởi tạo và có thể nếu cài đặt văn hóa thay đổi. –

+0

* Bạn sẽ phải xây dựng cụm từ thông dụng sao cho nếu chuỗi không khớp, nó sẽ không phân tích cú pháp. * Điều đó khó hơn rất nhiều so với âm thanh. Regex để phân tích cú pháp * giá trị * 'byte' hợp lệ là đáng ngạc nhiên liên quan. Nếu bạn nhấn mạnh vào việc sử dụng Regex, bạn nên sử dụng chúng để lọc trước các công cụ rõ ràng là xấu, nhưng vẫn xử lý các ngoại lệ có thể nảy sinh với 'Parse'. –

Các vấn đề liên quan