向任何TextReader添加功能

str*_*ger 4 c# oop aop

我有一个Location表示流中某个位置的类.(该类未与任何特定流耦合.)位置信息将用于将令牌与我的解析器中的输入中的位置匹配,以允许向用户报告更好的错误.

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

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

手动位置跟踪

每次我需要读取时TextReader,我都会使用读取的数据调用tokenizer AdvanceStringLocation对象.

好处

  • 非常简单.
  • 没有阶级臃肿.
  • 无需重写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方法.

缺点

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

在我的情况下,我正在寻找最好的方法.我想要:

  • 使用位置跟踪不会破坏tokenizer方法.
  • 用户代码中不需要大量初始化.
  • 没有任何/很多样板/重复代码.
  • (也许)不TextReaderLocation班级结合.

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

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

我已经实现了"普通TextReader包装器"和"基于事件的TextReader包装器"方法,并且由于其缺点中提到的原因而对两者都不满意.

Tom*_*cek 5

我同意创建一个实现的包装器,将实现TextReader委托给底层,TextReader并为跟踪位置添加一些额外的支持是一种很好的方法.您可以将其视为装饰器模式,因为您正在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;
  }
}
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携带的值将是当前行数(在文本中).