gpt4 book ai didi

c# - 向任何 TextReader 添加功能

转载 作者:太空狗 更新时间:2023-10-30 00:58:35 26 4
gpt4 key购买 nike

我有一个 Location 类,它表示流中某处的位置。 (该类未耦合到任何特定流。)位置信息将用于将标记与我的解析器中的输入位置相匹配,以便向用户报告更好的错误。

我想将位置跟踪添加到 TextReader 实例。这样,在读取 token 时,我可以获取位置(在读取数据时由 TextReader 更新)并在 token 化过程中将其提供给 token 。

我正在寻找实现此目标的好方法。我想出了几个设计。

手动位置跟踪

每次我需要从 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.
}

优势

  • token 化不知道位置跟踪。

缺点

  • 除了他们的 TextReader 实例之外,用户还需要创建和配置一个 LocatedTextReaderWrapper 实例。
  • 不允许在没有包装层的情况下添加不同类型的跟踪器或不同的位置跟踪器。

基于事件的 TextReader 包装器

类似于 LocatedTextReaderWrapper,但将其与 Location 对象分离,每当读取数据时都会引发事件。

优势

  • 可重复用于其他类型的跟踪。
  • token 化不知道位置跟踪或其他跟踪。
  • 可以同时跟踪多个独立的 Location 对象(或其他跟踪方法)。

缺点

  • 需要样板代码才能启用位置跟踪。
  • 除了他们的 TextReader 实例之外,用户还需要创建和处理包装器实例。

面向方面的方法

使用 AOP 来执行类似于基于事件的包装器方法。

优势

  • 可重复用于其他类型的跟踪。
  • token 化不知道位置跟踪或其他跟踪。
  • 无需重写 TextReader 方法。

缺点

  • 需要外部依赖,我想避免。

我正在寻找适合我情况的最佳方法。我愿意:

  • 不要通过位置跟踪使分词器方法膨胀。
  • 不需要在用户代码中进行大量初始化。
  • 没有任何/太多样板/重复代码。
  • (也许)不要将 TextReaderLocation 类耦合。

欢迎任何对此问题的见解以及可能的解决方案或调整。谢谢!

(对于那些想要具体问题的人:包装 TextReader 功能的最佳方法是什么?)

我已经实现了“Plain TextReader wrapper”和“Event-based TextReader wrapper”方法,我对这两种方法都不满意,原因在它们的缺点中提到.

最佳答案

我同意创建一个实现 TextReader 的包装器,将实现委托(delegate)给底层 TextReader 并添加一些额外的位置跟踪支持是一个好方法。您可以将其视为 Decorator pattern ,因为您正在用一些附加功能装饰 TextReader 类。

更好的包装:您可以用更简单的方式编写它,将 TextReaderLocation 类型分离 - 这样您就可以轻松修改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 读取一些字符串时都会触发该事件,并且该事件携带的值将是当前行数 (在文中)。

关于c# - 向任何 TextReader 添加功能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2548012/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com