我有一个Location
表示流中某个位置的类.(该类未与任何特定流耦合.)位置信息将用于将令牌与我的解析器中的输入中的位置匹配,以允许向用户报告更好的错误.
我想将位置跟踪添加到TextReader
实例.这样,在读取令牌时,我可以获取位置(由TextReader
读取的数据更新)并在令牌化过程中将其提供给令牌.
我正在寻找一个实现这一目标的好方法.我想出了几个设计.
每次我需要读取时TextReader
,我都会使用读取的数据调用tokenizer AdvanceString
的Location
对象.
TextReader
方法.TextReader
包装纸创建一个LocatedTextReaderWrapper
围绕每个方法调用的类,跟踪Location
属性.例:
public class LocatedTextReaderWrapper : TextReader {
private TextReader source;
public Location Location {
get;
set;
}
public LocatedTextReaderWrapper(TextReader source) :
this(source, new Location()) {
}
public LocatedTextReaderWrapper(TextReader source, Location location) {
this.Location = location;
this.source = source;
}
public override int Read(char[] buffer, int index, int count) {
int ret = this.source.Read(buffer, index, count);
if(ret >= 0) {
this.location.AdvanceString(string.Concat(buffer.Skip(index).Take(count)));
}
return ret;
}
// etc.
}
Run Code Online (Sandbox Code Playgroud)
LocatedTextReaderWrapper
除了实例之外,用户还需要创建和部署TextReader
实例.TextReader
包装器喜欢LocatedTextReaderWrapper
,但是Location
只要读取数据就将它与引发事件的对象分离.
Location
跟踪多个独立对象(或其他跟踪方法).TextReader
实例.使用AOP执行类似于基于事件的包装器方法.
TextReader
方法.在我的情况下,我正在寻找最好的方法.我想要:
TextReader
和Location
班级结合.欢迎任何有关此问题的见解以及可能的解决方案或调整.谢谢!
(对于那些想要特定问题的人:包装功能的最佳方法是TextReader
什么?)
我已经实现了"普通TextReader
包装器"和"基于事件的TextReader
包装器"方法,并且由于其缺点中提到的原因而对两者都不满意.
我同意创建一个实现的包装器,将实现TextReader
委托给底层,TextReader
并为跟踪位置添加一些额外的支持是一种很好的方法.您可以将其视为装饰器模式,因为您正在TextReader
使用一些附加功能来装饰该类.
更好的包装:您可以用更简单的方式编写它以将您TextReader
与Location
类型分离- 这样您就可以轻松地Location
单独修改,甚至提供基于跟踪阅读进度的其他功能.
interface ITracker {
void AdvanceString(string s);
}
class TrackingReader : TextReader {
private TextReader source;
private ITracker tracker;
public TrackingReader(TextReader source, ITracker tracker) {
this.source = source;
this.tracker = tracker;
}
public override int Read(char[] buffer, int index, int count) {
int res = base.Read(buffer, index, count);
tracker.AdvanceString(buffer.Skip(index).Take(res);
return res;
}
}
Run Code Online (Sandbox Code Playgroud)
我还会将跟踪器的创建封装到一些工厂方法中(这样您在应用程序中只有一个处理构造的位置).请注意,您可以使用此简单设计创建一个TextReader
报告多个Location
对象的进度:
static TextReader CreateReader(TextReader source, params ITracker[] trackers) {
return trackers.Aggregate(source, (reader, tracker) =>
new TrackingReader(reader, tracker));
}
Run Code Online (Sandbox Code Playgroud)
这将创建一个TrackingReader对象链,并且每个对象都会将读取的进度报告给作为参数传递的其中一个跟踪器.
关于基于事件:我认为在.NET库中使用标准的.NET/C#事件并不常见,但我认为这种方法也可能非常有趣 - 特别是在C#3中你可以使用lambda表达式甚至Reactive Framework等功能.
简单使用不会增加太多锅炉板:
using(ReaderWithEvents reader = new ReaderWithEvents(source)) {
reader.Advance += str => loc.AdvanceString(str);
// ..
}
Run Code Online (Sandbox Code Playgroud)
但是,您也可以使用Reactive Extensions处理事件.要计算源文本中新行的数量,您可以编写如下内容:
var res =
(from e in Observable.FromEvent(reader, "Advance")
select e.Count(ch => ch == '\n')).Scan(0, (sum, current) => sum + current);
Run Code Online (Sandbox Code Playgroud)
这将为您提供一个事件res
,每次从中读取一些字符串时都会触发该事件,事件所TextReader
携带的值将是当前行数(在文本中).