Правильно ли реализованы IEnumerator's для чтения CSV?

158
07 декабря 2021, 02:10

Я реализовал простенький IEnumerable<CSVLine> для чтения CSV файлов.

Правильно ли реализованы IEnumerator<>ы?

CSVStream:

public class CSVStream : IEnumerable<CSVLine>
{
    private readonly char[] _separators;
    public IReadOnlyCollection<char> Separators
    {
        get { return _separators; }
    }
    public CSVStream(string filePath, Encoding encoding = null, params char[] separators)
    {
        Encoding = encoding ?? Encoding.UTF8;
        _separators = separators ?? new []{','};
        FilePath = filePath;
    }
    public Encoding Encoding { get; }
    public string FilePath { get; }
    public IEnumerator<CSVLine> GetEnumerator()
    {
        return new CSVStreamEnumerator(FilePath, _separators, Encoding);
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

CSVStreamEnumerator:

public class CSVStreamEnumerator : IEnumerator<CSVLine>
{
    private readonly StreamReader _fileStream;
    private CSVLine _current;
    private readonly char[] _separators;
    public CSVStreamEnumerator(string filePath, char[] separators, Encoding encoding = null)
    {
        Encoding = encoding ?? Encoding.UTF8;
        _separators = separators;
        _fileStream =
            new StreamReader(filePath, Encoding);
    }
    public Encoding Encoding { get; }
    public bool IsDisposed { get; protected set; }
    public bool MoveNext()
    {
        string currentString = _fileStream.ReadLine();
        return !((currentString is null
            ? _current = null
            : _current = new CSVLine(currentString, _separators)) is null);
    }
    public void Reset()
    {
        _fileStream.DiscardBufferedData();
        _fileStream.BaseStream.Seek(0, SeekOrigin.Begin);
        _current = null;
    }
    public CSVLine Current
    {
        get
        {
            if (_fileStream == null || _current == null)
            {
                throw new InvalidOperationException();
            }
            return _current;
        }
    }
    object IEnumerator.Current
    {
        get { return Current; }
    }
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool disposing)
    {
        if (IsDisposed)
        {
            return;
        }
        _current = null;
        _fileStream?.Close();
        _fileStream?.Dispose();
        IsDisposed = true;
    }
    ~CSVStreamEnumerator()
    {
        Dispose(false);
    }
}

CSVLine:

public class CSVLine : IEnumerable<string>
{
    private readonly string[] _values;
    public CSVLine(string value, char[] separators)
    {
        if (value == null)
        {
            throw new ArgumentNullException(nameof(value));
        }
        _values = value.Split
        (
            separators ?? new[] {','},
            StringSplitOptions.None
        ).Select(s => s.Replace("\"\"", "\x0000").Trim('\"').Replace("\x0000", "\"")).ToArray();
    }
    public CSVLine(string[] values)
    {
        _values = values;
    }
    public int Count
    {
        get { return _values.Length; }
    }
    public IEnumerator<string> GetEnumerator()
    {
        return new CSVLineEnumerator(_values);
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
    public static implicit operator CSVLine(string val)
    {
        return new CSVLine(value: val, new []{','});
    }
    public static implicit operator CSVLine(string[] values)
    {
        return new CSVLine(values);
    }
}

CSVLineEnumerator

public class CSVLineEnumerator : IEnumerator<string>
{
    private int _currentIndex;
    private string[] _values;
    public CSVLineEnumerator(string[] values)
    {
        _values = values;
        _currentIndex = -1;
    }
    public bool IsDisposed { get; protected set; }
    public bool MoveNext()
    {
        if (_currentIndex == _values.Length - 1)
        {
            return false;
        }
        _currentIndex++;
        return true;
    }
    public void Reset()
    {
        _currentIndex = 0;
    }
    public string Current
    {
        get { return _values[_currentIndex]; }
    }
    object IEnumerator.Current
    {
        get { return Current; }
    }
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool disposing)
    {
        if (IsDisposed)
        {
            return;
        }
        if (disposing)
        {
            _values = null;
        }
        IsDisposed = true;
    }
    ~CSVLineEnumerator()
    {
        Dispose(false);
    }
}

Чтение через foreach:

[TestMethod]
public void EnumeratorTest()
{
    CSVStream stream = new CSVStream("D:\\test.csv", Encoding.UTF8, ';');
    foreach (CSVLine csvLine in stream)
    {
        foreach (string value in csvLine)
        {
            Console.Out.Write("\'{0}\' ", value);
        }
        Console.Out.WriteLine(Environment.NewLine);
    }
}

За основу взял тестовый текст из википедии:

1965;Пиксель;E240 – формальдегид (опасный консервант)!;"красный, зелёный, битый";"3000,00"
1965;Мышка;"А правильней использовать ""Ёлочки""";;"4900,00"
"Н/д";Кнопка;Сочетания клавиш;"MUST USE! Ctrl, Alt, Shift";"4799,00"

На выходе получил такйо результат:

'1965' 'Пиксель' 'E240 – формальдегид (опасный консервант)!' 'красный, зелёный, битый' '3000,00' 
'1965' 'Мышка' 'А правильней использовать "Ёлочки"' '' '4900,00' 
'Н/д' 'Кнопка' 'Сочетания клавиш' 'MUST USE! Ctrl, Alt, Shift' '4799,00'

P.S. Знаю что вот этот кусок кода:

_values = value.Split
(
    separators ?? new[] {','},
    StringSplitOptions.RemoveEmptyEntries
).Select(s => s.Replace("\"\"", "\x0000").Trim('\"').Replace("\x0000", "\"")).ToArray();

Выглядит как "бред сивой кобылы", но другого способа безболезненого удаления двойных кавычек и удаления ковычек из теста я не нашел =).

READ ALSO
Передача active из листа типа GameObject в лист bool

Передача active из листа типа GameObject в лист bool

Объясните как передать active из листа с типом GameObject в лист типа bool ?

221
Prism.WPF &amp; DryIoc, как регистрировать типы с фабрикой?

Prism.WPF & DryIoc, как регистрировать типы с фабрикой?

Доброго времени сутокВозникла проблема с регистрацией типов в IContainerRegistry с фабрикой

146
Привязки трех вложенных списокв

Привязки трех вложенных списокв

Подскажите пожалуйста как привязать 3 вложенных списка:

161
Чем выполнить JS?

Чем выполнить JS?

Средствами POST запроса, от сервера приходит объект JSON

85