Looking for a way to efficiently use 3rd party CSV reader without making a dependency on it. I would also prefer to make my class be available everywhere (not just in IoC container injectable Services), so I decided to go with a questionable decision – configure through the static property. Can you see a way to reduce amount of library code or make configuration more obvious without losing library handiness?
Repository at GitHub.
Project dependencies are:
And solution looks like this:
MyProject.Demo registers CSV library parser dependency and parses the CSV file:
class Program
{
static void Main(string[] args)
{
CsvRowReader.Use();
var text = "First,Last\nJohn,Doe\n";
var file = NameFile.Parse(text);
foreach (var name in file)
WriteLine($"{name.First} {name.Last}");
}
}
Business logic assembly MyProject defines NameFile
as:
public class NameFile : RowFile<NameFile, FullName>
{
protected override IEnumerable<FullName> Read(RowReader reader)
{
using(reader)
while (reader.Read())
yield return new FullName
{
First = reader.Get<string>("First"),
Last = reader.Get<string>("Last")
};
}
}
Where FullName
is:
public class FullName
{
public string First { get; set; }
public string Last { get; set; }
}
Using library class RowFile<TFile, TRow>
:
public abstract class RowFile<TFile, TRow> : Enumerable<TRow>
where TFile : RowFile<TFile, TRow>, new()
{
public static readonly TFile Empty = new TFile();
public static TFile Parse(string text) =>
Load(new StringReader(text));
public static TFile Load(string filePath) =>
Load(File.OpenText(filePath));
public static TFile Load(Stream stream) =>
Load(new StreamReader(stream));
public static TFile Load(TextReader reader)
{
var file = new TFile();
file.Rows = file.Read(RowReader.Create(reader));
return file;
}
public sealed override IEnumerator<TRow> GetEnumerator() =>
Rows.GetEnumerator();
protected abstract IEnumerable<TRow> Read(RowReader read);
IEnumerable<TRow> Rows { get; set; } = new TRow[0];
}
Where:
public abstract class RowReader : IDisposable
{
public static Func<TextReader, RowReader> Create { get; protected set; }
public abstract void Dispose();
public abstract bool Read();
public abstract T Get<T>(string name);
}
And:
public abstract class Enumerable<T> : IEnumerable<T>
{
public abstract IEnumerator<T> GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
}
3rd party CSV reader dependency is incapsulated in MyProject.CsvHelper project:
public class CsvRowReader : RowReader
{
public static void Use() =>
Create = reader => new CsvRowReader(reader);
CsvRowReader(TextReader reader)
{
Reader = new CsvReader(reader);
Reader.Read();
Reader.ReadHeader();
}
CsvReader Reader { get; }
public override void Dispose() =>
Reader.Dispose();
public override bool Read() =>
Reader.Read();
public override T Get<T>(string name) =>
Reader.GetField<T>(name);
}
I don’t like all those mutability things, but do not see a better solution at the moment...
-
\$\begingroup\$ Could you point me (us?) to those mutability things? I'm not sure I see the issue in this design. \$\endgroup\$t3chb0t– t3chb0t2018年06月19日 06:12:06 +00:00Commented Jun 19, 2018 at 6:12
-
\$\begingroup\$ @t3chb0t RowFile.Rows & RowReader.Create properties are both mutable. I understand Create but Rows looks a little bit ugly :) \$\endgroup\$Dmitry Nogin– Dmitry Nogin2018年06月19日 06:25:23 +00:00Commented Jun 19, 2018 at 6:25
1 Answer 1
public static TFile Load(TextReader reader) { var file = new TFile(); file.Rows = file.Read(RowReader.Create(reader)); return file; }
In order to get rid of the mutable property file.Rows
I suggest taking a look at Autofac's factory delegate for creating TFile
immutably and injecting this delegate as a dependency.
Alternatively you could register them via named and keyed services and pick the right factory by TKey
with Type
as key. I use this techniques quite often.
I guess other dependency injection frameworks could do similar magic.
-
\$\begingroup\$ I went with settable property to skip non-default ctor creation. This way there is no need to redefine delegating ctor again and again for each concrete File class... Not sure if there is an other way to do it. \$\endgroup\$Dmitry Nogin– Dmitry Nogin2018年06月19日 14:03:58 +00:00Commented Jun 19, 2018 at 14:03
-
\$\begingroup\$ @DmitryNogin I find this vicious-circle a little bit funny :-) you're saying the mutable
Rows
property is ugly but at the same time you actually refuse to unuglyfy it with proper dependency injection because you want a simple creation viawhere T : new()
- this is a clear case of the catch-22 :-P \$\endgroup\$t3chb0t– t3chb0t2018年06月19日 14:15:49 +00:00Commented Jun 19, 2018 at 14:15 -
\$\begingroup\$ Yep, exactly :) There will be need in Activator.CreateInstance, etc for non default ctors. There is no way in C# to define TFile type parameter constraint for non-default ctor, so it would be ugly too :) \$\endgroup\$Dmitry Nogin– Dmitry Nogin2018年06月19日 14:21:19 +00:00Commented Jun 19, 2018 at 14:21