I wanted to parse lines of text as tab-delimited items, and I had just been using a StringReader for something else and I came up with this:
class TabDelimitedFieldReader : StringReader
{
private string _nextLine;
public TabDelimitedFieldReader(string s)
: base(s)
{
}
public IEnumerable<string> ReadFields()
{
if (_nextLine != null)
{
var fields = _nextLine.Split('\t');
foreach (string field in fields)
{
yield return field;
}
}
}
public bool HasMore
{
get
{
_nextLine = this.ReadLine();
return (_nextLine != null);
}
}
}
I can't think of another time I directly subclasses a .NET framework class like this. I figure maybe I'm just strange and it's normal for others, or maybe there's a reason I wouldn't want to do it.
1 Answer 1
Conceptually, I do not see anything inherently wrong with subclassing a framework object, particularly since many of them provide virtual
/abstract
members specifically to allow it.
In this case, it may be better to wrap a TextReader
object instead of inheriting from StringReader
. This would allow you to provide tab delimited reading for multiple data sources instead of limiting yourself to in-memory strings. For example, you could still pass in a StringReader
for reading strings, but you could also pass in a StreamReader
if you wanted to support reading from files instead.
Additionally, I noticed that HasMore
is dangerous to call due to its side-effect. It is not immediately obvious to a caller that checking HasMore
would alter the reader's current position. I would suggest using Peek
instead.
You would then need to update _nextLine
elsewhere. You could have it be a side-effect of calling ReadFields
. Or, perhaps better, you could add another method (e.g., AdvanceLine
) that is simply responsible for moving the reader to the next line.
-
\$\begingroup\$ Thanks for the feedback - question: are you a TDD developer? I'm not but have been lightly using/learning it for a while, and from my limited understanding, code that works is the goal and pre-empting the future is frowned upon... which goes against my instincts too, but seems to be the primary directive (as I understand it). \$\endgroup\$Aaron Anodide– Aaron Anodide2012年02月09日 18:00:49 +00:00Commented Feb 9, 2012 at 18:00
-
2\$\begingroup\$ By wrapping the class, you are actually reducing the test surface area, as you only need test your externally visible (public/protected) members. By extending StringReader, you would be stuck testing for cases where callers use the public base class methods to ensure you properly handle side-effects. For example, if someone called ReadLine (from TextReader), it would skip a line, changing expected behavior for either ReadFields or HasMore (or both!). \$\endgroup\$Dan Lyons– Dan Lyons2012年02月10日 17:53:40 +00:00Commented Feb 10, 2012 at 17:53
-
\$\begingroup\$ @AaronAnodide it's called the decorator pattern. All stream in .NET work in this way. For example you can create a
MemoryStream
from aFileStream
. \$\endgroup\$t3chb0t– t3chb0t2016年11月11日 13:54:05 +00:00Commented Nov 11, 2016 at 13:54
ReadFields
function which would cause the fields to be trimmed iftrue
. I would run StyleCop on this, which would force you to add comments and rename_nextLine
tonextLine
, and refer to it asthis.nextLine
, etc. \$\endgroup\$