|
Introduction
One would imagine that parsing CSV files is a straightforward and boring task. I was thinking that too, until I had to parse several CSV files of a couple GB each. After trying to use the OleDB JET driver and various regular expressions, I still ran into serious performance problems. At this point, I decided I would try the custom class option. I scoured the net for existing code, but finding a correct, fast, and efficient CSV parser and reader is not so simple, whatever platform/language you fancy.
I say correct in the sense that many implementations merely use some splitting method like String.Split(). This will, obviously, not handle field values with commas. Better implementations may care about escaped quotes, trimming spaces before and after fields, etc., but none I found were doing it all, and more importantly, in a fast and efficient manner.
And so, this led to the CSV reader class I present in this article. Its design is based on the System.IO.StreamReader class, and so is a non-cached, forward-only reader (similar to what is sometimes called a fire-hose cursor).
Benchmarking it against both OLEDB and regex methods, it performs about 15 times faster, and yet its memory usage is very low.
To give more down to earth numbers, with a 45 MB CSV file containing 145 fields and 50,000 records, the reader was processing about 30 MB/sec. So all in all, it took 1.5 seconds! The machine specs were P4 3.0 GHz, 1024 MB.
Supported Features
This reader supports fields spanning multiple lines. The only restriction is that they must be quoted, otherwise it would not be possible to distinguish between malformed data and multi-line values.
Basic data-binding is possible via the System.Data.IDataReader interface implemented by the reader.
You can specify custom values for these parameters:
- Default missing field action;
- Default malformed CSV action;
- Buffer size;
- Field headers option;
- Trimming spaces option;
- Field delimiter character;
- Quote character;
- Escape character (can be the same as the quote character);
- Commented line character.
If the CSV contains field headers, they can be used to access a specific field.
When the CSV data appears to be malformed, the reader will fail fast and throw a meaningful exception stating where the error occurred and providing the current content of the buffer.
A cache of the field values is kept for the current record only, but if you need dynamic access, I also included a cached version of the reader, CachedCsvReader, which internally stores records as they are read from the stream. Of course, using a cache this way makes the memory requirements way higher, as the full set of data is held in memory.
Fixes
- Fixed a bug when calling MoveTo in a particular action sequence;
- Fixed a bug when extra fields are present in a multiline record;
- Fixed a bug when there is a parse error while initializing;
Benchmark and Profiling
You can find the code for these benchmarks in the demo project. I tried to be fair and follow the same pattern for each parsing method. The regex used comes from Jeffrey Friedl's book, and can be found at page 271. It doesn't handle trimming and multi-line fields.
The test file contains 145 fields, and is about 45 MB (included in the demo project as a RAR archive).
I also included the raw data from the benchmark program and from the CLR Profiler for .NET 2.0.


Using the Code
The class design follows System.IO.StreamReader as much as possible. The parsing mechanism introduced in version 2.0 is a bit trickier because we handle the buffering and the new line parsing ourselves. Nonetheless, because the task logic is clearly encapsulated, the flow is easier to understand. All the code is well documented and structured, but if you have any questions, simply post a comment.
Basic Usage Scenario
using System.IO;
using LumenWorks.Framework.IO.Csv;
void ReadCsv()
{
using (CsvReader csv =
new CsvReader(new StreamReader("data.csv"), true))
{
int fieldCount = csv.FieldCount;
string[] headers = csv.GetFieldHeaders();
while (csv.ReadNextRecord())
{
for (int i = 0; i < fieldCount; i++)
Console.Write(string.Format("{0} = {1};",
headers[i], csv[i]));
Console.WriteLine();
}
}
}
Simple Data-Binding Scenario (ASP.NET)
using System.IO;
using LumenWorks.Framework.IO.Csv;
void ReadCsv()
{
using (CsvReader csv = new CsvReader(
new StreamReader("data.csv"), true))
{
myDataRepeater.DataSource = csv;
myDataRepeater.DataBind();
}
}
Complex Data-Binding Scenario (ASP.NET)
Due to the way both the System.Web.UI.WebControls.DataGrid and System.Web.UI.WebControls.GridView handle System.ComponentModel.ITypedList, complex binding in ASP.NET is not possible. The only way around this limitation would be to wrap each field in a container implementing System.ComponentModel.ICustomTypeDescriptor.
Anyway, even if it was possible, using the simple data-binding method is much more efficient.
For the curious amongst you, the bug comes from the fact that the two grid controls completely ignore the property descriptors returned by the System.ComponentModel.ITypedList, and relies instead on System.ComponentModel.TypeDescriptor.GetProperties(...), which obviously returns the properties of the string array and not our custom properties. See System.Web.UI.WebControls.BoundColumn.OnDataBindColumn(...) in a disassembler.
Complex Data-Binding Scenario (Windows Forms)
using System.IO;
using LumenWorks.Framework.IO.Csv;
void ReadCsv()
{
using (CachedCsvReader csv = new
CachedCsvReader(new StreamReader("data.csv"), true))
{
myDataGrid.DataSource = csv;
}
}
Custom Error Handling Scenario
using System.IO;
using LumenWorks.Framework.IO.Csv;
void ReadCsv()
{
using (CsvReader csv = new CsvReader(
new StreamReader("data.csv"), true))
{
csv.MissingFieldAction = MissingFieldAction.ReplaceByNull;
int fieldCount = csv.FieldCount;
string[] headers = csv.GetFieldHeaders();
while (csv.ReadNextRecord())
{
for (int i = 0; i < fieldCount; i++)
Console.Write(string.Format("{0} = {1};",
headers[i],
csv[i] == null ? "MISSING" : csv[i]));
Console.WriteLine();
}
}
}
Custom Error Handling Using Events Scenario
using System.IO;
using LumenWorks.Framework.IO.Csv;
void ReadCsv()
{
using (CsvReader csv = new CsvReader(
new StreamReader("data.csv"), true))
{
csv.DefaultParseErrorAction = ParseErrorAction.RaiseEvent;
csv.ParseError += new ParseErrorEventHandler(csv_ParseError);
int fieldCount = csv.FieldCount;
string[] headers = csv.GetFieldHeaders();
while (csv.ReadNextRecord())
{
for (int i = 0; i < fieldCount; i++)
Console.Write(string.Format("{0} = {1};",
headers[i], csv[i]));
Console.WriteLine();
}
}
}
void csv_ParseError(object sender, ParseErrorEventArgs e)
{
if (e.Error is MissingFieldException)
{
Console.Write("--MISSING FIELD ERROR OCCURRED");
e.Action = eErrorAction.AdvanceToNextLine;
}
}
History
Version 3.6.2 (2008-10-09)
- Fixed a bug when calling MoveTo in a particular action sequence;
- Fixed a bug when extra fields are present in a multiline record;
- Fixed a bug when there is a parse error while initializing;
Version 3.6.1 (2008-07-16)
- Fixed a bug with RecordEnumerator caused by reusing the same array over each iteration;
Version 3.6 (2008-07-09)
- Added a web demo project;
- Fixed a bug when loading CachedCsvReader into a DataTable and the CSV has no header;
Version 3.5 (2007-11-28)
- Fixed a bug when initializing CachedCsvReader without having read a record first;
Version 3.4 (2007-10-23)
- Fixed a bug with IDataRecord implementation where GetValue/GetValues should return DBNull.Value when the field value is empty or null;
- Fixed a bug where no exception is raised if a delimiter is not present after a non final quoted field;
- Fixed a bug when trimming unquoted fields and whitespaces span over 2 buffers;
Version 3.3 (2007-01-14)
- Added the option to turn off skipping empty lines via the property
SkipEmptyLines (on by default);
- Fixed a bug with the handling of a delimiter at the end of a record preceded by a quoted field;
Version 3.2 (2006-12-11)
- Modified slightly the way missing fields are handled;
- Fixed a bug where the call to
CsvReader.ReadNextRecord() would return false for a CSV file containing only one line ending with a new line character and no header;
Version 3.1.2 (2006-08-06)
- Updated dispose pattern;
- Fixed a bug when SupportsMultiline is false;
- Fixed a bug where the
IDataReader schema column "DataType" returned DbType.String instead of typeof(string);
Version 3.1.1 (2006-07-25)
- Added a
SupportsMultiline property to help boost performance when multi-line support is not needed;
- Added two new constructors to support common scenarios;
- Added support for when the base stream returns a length of 0;
- Fixed a bug when the
FieldCount property is accessed before having read any record;
- Fixed a bug when the delimiter is a whitespace;
- Fixed a bug in
ReadNextRecord(...) by eliminating its recursive behavior when initializing headers;
- Fixed a bug when EOF is reached when reading the first record;
- Fixed a bug where no exception would be thrown if the reader has reached EOF and a field is missing.
Version 3.0 (2006-05-15)
- Introduced equal support for .NET 1.1 and .NET 2.0;
- Added extensive support for malformed CSV files;
- Added complete support for data-binding;
- Made available the current raw data;
- Field headers are now accessed via an array (breaking change);
- Made field headers case insensitive (thanks to Marco Dissel for the suggestion);
- Relaxed restrictions when the reader has been disposed;
CsvReader supports 2^63 records;
- Added more test coverage;
- Upgraded to .NET 2.0 release version;
- Fixed an issue when accessing certain properties without having read any data (notably
FieldHeaders).
Version 2.0 (2005-08-10)
- Ported code to .NET 2.0 (July 2005 CTP);
- Thoroughly debugged via extensive unit testing (special thanks to shriop);
- Improved speed (now 15 times faster than OLEDB);
- Consumes half the memory than version 1.0;
- Can specify a custom buffer size;
- Full Unicode support;
- Auto-detects line ending, be it \r, \n, or \r\n;
- Better exception handling;
- Supports the "field1\rfield2\rfield3\n" pattern (used by Unix);
- Parsing code completely refactored, resulting in much cleaner code.
Version 1.1 (2005-01-15)
- 1.1: Added support for multi-line fields.
Version 1.0 (2005-01-09)
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 1,074 (Total in Forum: 1,074) (Refresh) | FirstPrevNext |
|
 |
|
|
After reading a CSV file and I execute the following code:
foreach (string[] s in csv) { Console.Out.WriteLine(s); }
the second line in the CSV file seems to be skipped; for example
a,b b,c <-- this line is skipped in interation d,e
here is the complete code segment if you are interested
using (TextReader reader = new StreamReader(@"C:\test.csv")) { using (CachedCsvReader csv = new CachedCsvReader(new StringReader( reader.ReadToEnd()), true)) { dataGrid1.DataSource = csv;
foreach (string[] s in csv) { Console.Out.WriteLine(s); } } }
and the CSV file in question (test.csv)
Input,Expected output,comparison Type,input reason,output reason,fatal,timeout miliseconds,msg on error,tags aa1\r,>,contains,disable security,the prompt showing security has been disabled,TRUE,500,could not disable security,OBD aa1\r,>,contains,disable security,the prompt showing security has been disabled,TRUE,500,could not disable security,OBD aa1\r,>,contains,disable security,the prompt showing security has been disabled,TRUE,500,could not disable security,OBD aa1\r,>,contains,disable security,the prompt showing security has been disabled,TRUE,500,could not disable security,OBD
I am using the .dlls from the download, compiling the source code seems to work fine
do you experience this? let me know if I can post any more details
Awesome utility by the way -- saved me a BUNCH of time 
Scott www.fmsgps.com
modified on Thursday, January 8, 2009 8:26 PM
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Well, first of all, you could rewrite your code as follow, which will save you the call to ReadToEnd.
using (CachedCsvReader csv = new CachedCsvReader(new StreamReader(@"C:\test.csv"), true)) { dataGrid1.DataSource = csv;
foreach (string[] s in csv) { Console.Out.WriteLine(s); } }
As for the possible, I will check that as soon as I get a chance.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Loss of special foreign country (Hungarian) characters, can you help in fixing this, please?
for example:
áéíóöőúüű ÁÉÍŐÖŐÚÜŰ
Thanks in advance, Attila
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Thanks for the parser!
I'd like to have a record number property that returns the number based on the input file (including blank, comments and header lines) so that if an error is encountered, it can be reported to the user and he can locate the bad record in a text editor based on the line number.
Also, it would be nice to have a property for the contents of the current row being processed; useful for logging and/or error reporting.
Thanks, Gary Davis
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
If the csv is malformed, the exception that is thrown already has such info (look at its properties). Would that be enough for you? If not, I can always add this info as properties.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Well, the csv may be perfectly fine but the data is bad (e.g. "account number not found in database") so it would be nice to tell the user which lines in the input file need to be fixed.
Gary
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I see what you mean. Well, there is the method GetCurrentRawData which I forgot about, and also the property CurrentRecordIndex. The reader does not have a notion of current field index per se, but you should know which field index you are at since you are the one looping through them.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thanks for going to extra mile, performance testing, unit testing, etc. 
I really like how you were cognizant about performance, all too often this is thrown to the way side.
R.Bischoff
Tengas un buen dia
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I was going through your code and I was getting lost in the ReadField and ReadNextRecord methods. The whole thing has lots and lots of features and all I need is to quickly parse a CSV file and push everything in a DataTable so I thought to write a stream that would simply parse a CSV file and return lists of String for each row.
It supports changing the CSV format (value, line delimiter and quote symbol) and buffers everything. It even supports multi-characters delimiter (and in theory multi character quote symbol, although I didn't test that). They cannot exceed the length of the buffer which is 1024 though (I'll have an exception thrown for that). And line break may not be handled correctly if your file used '\r'.
I'm just posting it here to see if someone thinks it's worth posting an article for.
REMEMBER : THIS IS NOT PRODUCTION CODE! This is just an example that demonstrates the concept. I need to refactor stuff (like the buffer management), add error handling and comment everything.
using System; using System.Collections.Generic; using System.Text; using System.IO;
namespace Utility {
/// /// A buffered stream used to retrieve information from a CSV file. /// public class CsvStream {
#region INNER CLASSES
private enum CsvToken { Content, ValueDelimiter, LineDelimiter, TextQuote }
#endregion
#region PROPERTIES
private TextReader reader = null; private CsvConfiguration configuration; private char[] buffer = null; private int contentLength = 0; private int bufferPosition = 0; private bool endOfLine = true;
#endregion
#region CONSTRUCTORS
public CsvStream(TextReader reader, CsvConfiguration configuration) { this.reader = reader; this.configuration = configuration; }
#endregion
#region METHODS
public bool NextRow() {
if (buffer == null) { endOfLine = false; RefreshBuffer(); return HasChars(1); }
while (ReadField() != null) { }
if (PeekNextToken() == CsvToken.LineDelimiter) { bufferPosition += configuration.LineDelimiter.Length; endOfLine = false; return true; } else { return false; } }
public List ReadToEndOfRow() { List row = new List(); String field = ReadField();
while (field != null) { row.Add(field); field = ReadField(); }
return row; }
protected String ReadField() { if (endOfLine) { return null; }
StringBuilder fieldBuilder = new StringBuilder(); bool quoted = false;
while (true) { switch (PeekNextToken()) { case CsvToken.Content: try { fieldBuilder.Append(ReadChar()); } catch (EndOfStreamException) { endOfLine = true;
if (fieldBuilder.Length > 0) { return fieldBuilder.ToString(); } else { return null; } }
break;
case CsvToken.ValueDelimiter: bufferPosition += configuration.ValueDelimiter.Length;
if (!quoted) { return fieldBuilder.ToString(); } else { fieldBuilder.Append(configuration.ValueDelimiter); }
break;
case CsvToken.LineDelimiter: if (!quoted) { endOfLine = true; return fieldBuilder.ToString(); } else { bufferPosition += configuration.LineDelimiter.Length;
fieldBuilder.Append(configuration.LineDelimiter); }
break;
case CsvToken.TextQuote: bufferPosition += configuration.TextQuote.Length;
if (!quoted) { quoted = true; } else { if (!fieldBuilder.ToString().EndsWith( configuration.TextQuote)) {
quoted = false; } else { CsvToken nextToken = PeekNextToken();
if (nextToken != CsvToken.ValueDelimiter && nextToken != CsvToken.LineDelimiter) {
fieldBuilder.Append( configuration.TextQuote); } } }
break; } } }
private char ReadChar() { if (!HasChars(1)) { RefreshBuffer(); }
if (HasChars(1)) { char letter = buffer[bufferPosition]; bufferPosition++;
return letter; } else { throw new EndOfStreamException(); } }
private bool HasChars(int count) { return (contentLength > bufferPosition + count - 1); }
private CsvToken PeekNextToken() { if (PeekIsNext(configuration.LineDelimiter)) { return CsvToken.LineDelimiter; } else if (PeekIsNext(configuration.ValueDelimiter)) { return CsvToken.ValueDelimiter; } else if (PeekIsNext(configuration.TextQuote)) { return CsvToken.TextQuote; }
return CsvToken.Content; }
private bool PeekIsNext(String subject) { if (!HasChars(subject.Length)) { RefreshBuffer();
if (!HasChars(subject.Length)) { return false; } }
for (int charIndex = 0; charIndex < subject.Length; charIndex++) { if (subject[charIndex] != buffer[bufferPosition + charIndex]) { return false; } }
return true; }
private void RefreshBuffer() { if (buffer == null) { buffer = new char[1024]; contentLength = reader.Read(buffer, 0, buffer.Length); } else if (bufferPosition > 0) { int moverPosition = 0;
while (bufferPosition < contentLength) { buffer[moverPosition] = buffer[bufferPosition];
moverPosition++; bufferPosition++; }
contentLength = moverPosition + reader.Read(buffer, moverPosition, buffer.Length - moverPosition);
bufferPosition = 0; } }
#endregion } }
And here's the code for the CsvConfiguration struct.
using System; using System.Collections.Generic; using System.Text;
namespace Utility {
/// /// A set of CSV representation configuration. /// public struct CsvConfiguration {
/// /// The value delimiter for the CSV file. /// public String ValueDelimiter;
/// /// The line delimiter for the CSV file. /// public String LineDelimiter;
/// /// The text quotation marker for the CSV file. /// public String TextQuote; } }
Tell me if you think it's worth posting an article, I can make it pretty and stable over the holidays and post some article explaining everything.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Two simple questions that will give you the answer you seek:
- Is your code much faster than mine ? - Is it correct, ie does it pass all the unit tests of my reader ?
I have tested my reader with very small and very large files, and it can handle both just fine performance wise, but if yours is an improvement, sure, why not, more choices cannot hurt (usually)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I wasn't targeting speed as much as simplest code. It doesn't target the same needs either, you can't data bind it or anything. It's just for reading the file and returning "List of String" objects for each rows in the file.
Yet, it is pretty fast and I've loaded 100 MB and over test files without problem within seconds (except the DataTable I was loading it into took 500 MB RAM, but my real need is a 2-3 MB max file so that's fine). I'll work on it during the holidays, benchmark stuff and compare it with other implementation here on CodeProject. Then I'll see if it's the fastest for the purpose.
Yet, I really think the code is simpler though which is the reason I got stuck from using your solution. I want to understand what is going on if a customer come in with his Chinese file or something and everything breaks apart...
Btw, I'm not criticizing your work! You did a really good job and you've implemented stuff I didn't even dream about! You get a 5 from me 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Unicode/fancy charset support comes from .NET, not this reader So if all hell breaks loose when reading Chinese stuff, it's either a problem with the configuration of the StreamReader that you pass to CsvReader or a .NET bug (ahem, I mean feature).
For data binding, CachedCsvReader does it in a limited way. Unfortunately, .NET is not compatible with itself (ignoring its own binding mechanism), so I thought, the reader is fine for Repeater and WinForms DataGridView, so for ASP.NET grid and more involved stuff, there is always DataView which is not that bad in .NET 2.0.
As for simplicity, surely you understand your code better than mine (that's quite normal), but I can tell you that it would take me some time to fully grasp yours (which is also normal). Yours is shorter (!= simpler), but that's a given since it does less and maybe does not handle all corner cases. The basic principle is simple though: the reader is parsing fields one by one in a streaming way, while managing its buffer. It would have been simpler if records were read as a whole instead of field by field, but that's too late to change.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Btw, I am not getting down on you, but simply bringing valid points to consider since you asked for feedback. I have no problem with reinventing the wheel, if it is indeed worth it.
And thanks for the vote, you are amongst the 0.05% that voted
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Well, I was curious to see how your code performed since it looked ok, so I copy/pasted your code into the benchmark project and created another benchmark class (see below). Your code performs much better than TextFieldParser (7x), OleDb (6x) and Regex (2x), but is still about 3 times slower than this reader.
The results:
Test pass #3 - All fields
CsvReader - No cache : 4277164 ticks, 1.1949 sec., 36.8235 MB/sec. Stonkie : 12382949 ticks, 3.4594 sec., 12.7191 MB/sec. CachedCsvReader - Run 1 : 10252767 ticks, 2.8643 sec., 15.3617 MB/sec. CachedCsvReader - Run 2 : 179 ticks, 0.0001 sec., 879888.1564 MB/sec. TextFieldParser : 70436703 ticks, 19.6776 sec., 2.2360 MB/sec. OleDb : 51263240 ticks, 14.3212 sec., 3.0724 MB/sec. Regex : 34794112 ticks, 9.7203 sec., 4.5266 MB/sec.
Test pass #3 - Field #72 (middle)
CsvReader - No cache : 2812913 ticks, 0.7858 sec., 55.9918 MB/sec. Stonkie : 12614349 ticks, 3.5240 sec., 12.4858 MB/sec. CachedCsvReader - Run 1 : 9594586 ticks, 2.6804 sec., 16.4155 MB/sec. CachedCsvReader - Run 2 : 188 ticks, 0.0001 sec., 837765.8511 MB/sec. TextFieldParser : 70008303 ticks, 19.5579 sec., 2.2497 MB/sec. OleDb : 22593327 ticks, 6.3118 sec., 6.9711 MB/sec. Regex : 15957049 ticks, 4.4578 sec., 9.8702 MB/sec.
The benchmark class:
public sealed class StonkieBenchmark { private StonkieBenchmark() { }
public static object Run(object[] args) { if (args.Length == 1) Run((string) args[0]); else Run((string) args[0], (int) args[1]);
return null; }
public static void Run(string path) { Run(path, -1); }
public static void Run(string path, int field) { CsvConfiguration config = new CsvConfiguration(); config.LineDelimiter = Environment.NewLine; config.ValueDelimiter = ","; config.TextQuote = "\"";
CsvStream csv = new CsvStream(new StreamReader(path), config); string s;
if (field == -1) { while (csv.NextRow()) { List<string> record = csv.ReadToEndOfRow(); for (int i = 0; i < record.Count; i++) s = record[i]; } } else { while (csv.NextRow()) { List<string> record = csv.ReadToEndOfRow(); s = record[field]; } } } }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, great library I use the compiled dll and thought I'd just point out that the file version is still at version 3.00 and the copyright at 2006. Has the dll been recompiled with the wrong values or is this an old version of the dll?
Damien
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I checked on the net and the copyright notice should show the first year of publication which is 2006 in my case. More over, for work published after 1989, a copyright notice is no longer mandatory under US laws (but still recommended). Have you information that tells otherwise ?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I don't know much about US law as we sell our products in UK & Ireland. We normally use the copyright format - Copyright © [company name], [first release year]-[last release year]
This also happens to be the same as codeproject has on their page footers.
To be honest I'm not even sure if this is the proper UK/Ireland copyright format.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Sebastien, I have a case that all the contents of csv file are required, thst is, the comment char funtion is not necessary.
As I try to use Csvreader constructor to pass null value as comment char argument, compile error occurs.
I trace the code, the only things I can do is to modify the code as follows:
private void DoSkipEmptyAndCommentedLines(ref int pos) { while (pos < _bufferLength) { //if (_buffer[pos] == _comment) //{ // pos++; // SkipToNextLine(ref pos); //} //else if (_skipEmptyLines && ParseNewLine(ref pos)) continue; else break; } } Would like to have your comment on this change, is there any easier way to disable comment char function? Thansk. 
|
| Sign In· | | | | | |