我有一个Location类,它表示流中某个位置。(类不耦合到任何特定的流。)位置信息将用于将标记与我解析器中输入中的位置相匹配,以便向用户报告更好的错误。
我想将位置跟踪添加到TextReader实例中。这样,在读取令牌时,我可以获取位置(在读取数据时由TextReader更新),并在令牌化过程中将其交给令牌。
我正在寻找一个很好的方法来实现这个目标。我想出了几种设计方案。
手动位置跟踪
每次需要从TextReader读取数据时,我都会使用读取的数据在令牌程序的Location对象上调用AdvanceString。
优势
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.
}优势
缺点
LocatedTextReaderWrapper实例之外,用户还需要创建和释放一个TextReader实例。基于事件的TextReader包装器
与LocatedTextReaderWrapper类似,但是只要读取数据,就会将其与引发事件的Location对象分离。
优势
Location对象(或其他跟踪方法)。缺点
TextReader实例之外,用户还需要创建和释放包装器实例。面向方面的方法
使用AOP执行类似于基于事件的包装方法。
优势
TextReader方法。缺点
在我的情况下,我正在寻找最好的方法。我想:
TextReader与Location类耦合。任何对这个问题的洞察力和可能的解决方案或调整都是受欢迎的。谢谢!
(对于那些想要一个特定问题的人:包装TextReader功能的最佳方法是什么?)
我已经实现了“简单的TextReader包装器”和“基于事件的TextReader包装器”方法,并且对这两种方法都不满意,因为它们的缺点中提到了一些原因。
发布于 2010-04-06 13:24:40
我同意创建一个实现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;
}
}我还会将跟踪器的创建封装到一些工厂方法中(以便在处理构造的应用程序中有一个位置)。请注意,您可以使用这个简单的设计来创建一个TextReader,它也向多个Location对象报告进度:
static TextReader CreateReader(TextReader source, params ITracker[] trackers) {
return trackers.Aggregate(source, (reader, tracker) =>
new TrackingReader(reader, tracker));
}这将创建一个TrackingReader对象链,每个对象都将读取进度报告给作为参数传递的跟踪器之一。
关于基于事件的:我认为在.NET库中使用标准的.NET/C#事件不是那么频繁,但我认为这种方法可能也很有趣--特别是在C# 3中,您可以使用诸如lambda表达式甚至是反应性框架之类的特性。
简单的使用不会增加太多的锅炉板:
using(ReaderWithEvents reader = new ReaderWithEvents(source)) {
reader.Advance += str => loc.AdvanceString(str);
// ..
}但是,您也可以使用反应性扩展来处理事件。要计算源文本中的新行数,可以编写如下内容:
var res =
(from e in Observable.FromEvent(reader, "Advance")
select e.Count(ch => ch == '\n')).Scan(0, (sum, current) => sum + current);这将为您提供一个事件res,每次从TextReader读取字符串时都会触发该事件,该事件所携带的值将是当前行数(在文本中)。
发布于 2010-03-30 19:45:31
我倾向于使用普通的TextReader包装器,因为它似乎是最自然的方法。另外,我认为你提到的缺点并不是真正的缺点:
嗯,如果用户需要跟踪,他无论如何都需要操纵与跟踪相关的对象,创建一个包装器也不是什么大不了的事……为了简化工作,您可以创建一个扩展方法,为任何TextReader创建一个跟踪包装器。
您可以始终使用Location对象的集合,而不是单个对象:
public class LocatedTextReaderWrapper : TextReader {
private TextReader source;
public IList<Location> Locations {
get;
private set;
}
public LocatedTextReaderWrapper(TextReader source, params Location[] locations) {
this.Locations = new List<Location>(locations);
this.source = source;
}
public override int Read(char[] buffer, int index, int count) {
int ret = this.source.Read(buffer, index, count);
if(ret >= 0) {
var s = string.Concat(buffer.Skip(index).Take(count));
foreach(Location loc in this.Locations)
{
loc.AdvanceString(s);
}
}
return ret;
}
// etc.
}发布于 2010-04-08 00:10:45
AFAIK,如果您使用PostSharp,它会将自己注入编译过程,并根据需要重写代码,这样您就不会有任何永久的依赖关系(除了要求API用户有PostSharp来编译您的源代码之外)。
https://stackoverflow.com/questions/2548012
复制相似问题