我有一个Location
类,它代表流中某个位置。 (该类未与任何特定流耦合。)位置信息将用于将令牌与我的解析器输入中的位置匹配,以便向用户报告更好的错误。
我想将位置跟踪添加到TextReader
实例。这样,在读取令牌时,我可以获取位置(在读取数据时由TextReader
更新)并在令牌化过程中将其提供给令牌。
我正在寻找一种实现这一目标的好方法。我想出了几个设计。
每当我需要阅读TextReader
时,我会在标记器的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.
}
LocatedTextReaderWrapper
实例外,用户还需要创建和部署TextReader
实例。TextReader
包装器与LocatedTextReaderWrapper
类似,但会在读取数据时将其与Location
对象解耦,从而引发事件。
Location
个对象(或其他跟踪方法)。TextReader
实例外,用户还需要创建和处理包装器实例。使用AOP执行类似于基于事件的包装器方法。
TextReader
方法。我正在寻找最适合我的情况。我想:
TextReader
与Location
类结合在一起。欢迎任何有关此问题的见解以及可能的解决方案或调整。谢谢!
(对于那些想要特定问题的人:包裹TextReader
功能的最佳方式是什么?)
我已经实现了“普通TextReader
包装器”和“基于事件的TextReader
包装器”方法,并且由于其缺点中提到的原因而对两者都不满意。 < / p>
答案 0 :(得分:5)
我同意创建一个实现TextReader
的包装器,将实现委托给底层TextReader
,并为跟踪位置添加一些额外的支持是一种很好的方法。您可以将其视为Decorator pattern,因为您正在使用其他一些功能来装饰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表达式甚至Reactive Framework等功能。
简单使用不会增加太多锅炉板:
using(ReaderWithEvents reader = new ReaderWithEvents(source)) {
reader.Advance += str => loc.AdvanceString(str);
// ..
}
但是,您也可以使用Reactive Extensions来处理事件。要计算源文本中新行的数量,您可以编写如下内容:
var res =
(from e in Observable.FromEvent(reader, "Advance")
select e.Count(ch => ch == '\n')).Scan(0, (sum, current) => sum + current);
这将为您提供一个事件res
,每次从TextReader
读取一些字符串时都会触发该事件,并且事件所携带的值将是当前行数(在文本中)。< / p>
答案 1 :(得分:1)
我会选择普通的TextReader
包装器,因为它似乎是最自然的方法。另外,我认为你提到的缺点不是缺点:
- 除了TextReader实例外,用户还需要创建和部署一个LocationTextReaderWrapper实例。
好吧,如果用户需要跟踪,他还是需要操纵跟踪相关的对象,创建包装器并不是一件大事......为了简化操作,你可以创建一个扩展方法来创建一个跟踪任何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.
}
答案 2 :(得分:0)
AFAIK,如果您使用PostSharp,它会将自身注入编译过程并根据需要重写代码,这样您就不会有任何永久的依赖关系(除了要求API用户使用PostSharp来编译您的源代码)。