首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >向任何TextReader添加功能

向任何TextReader添加功能
EN

Stack Overflow用户
提问于 2010-03-30 19:19:35
回答 3查看 655关注 0票数 4

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

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

我正在寻找一个很好的方法来实现这个目标。我想出了几种设计方案。

手动位置跟踪

每次需要从TextReader读取数据时,我都会使用读取的数据在令牌程序的Location对象上调用AdvanceString

优势

  • 非常简单。
  • 没有班级臃肿。
  • 不需要重写TextReader方法。

缺点

  • 将位置跟踪逻辑耦合到令牌化过程。
  • 很容易忘记跟踪一些东西(尽管单元测试在这方面有帮助)。
  • 膨胀了现有的代码。

普通TextReader包装器

创建围绕每个方法调用的LocatedTextReaderWrapper类,跟踪Location属性。示例:

代码语言:javascript
复制
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方法。

缺点

  • 需要外部依赖项,这是我想要避免的。

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

  • 而不是膨胀标记器方法的位置跟踪。
  • 不需要用户代码中的大量初始化。
  • 没有任何/多的样板/重复代码。
  • (可能)没有将TextReaderLocation类耦合。

任何对这个问题的洞察力和可能的解决方案或调整都是受欢迎的。谢谢!

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

我已经实现了“简单的TextReader包装器”和“基于事件的TextReader包装器”方法,并且对这两种方法都不满意,因为它们的缺点中提到了一些原因。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2010-04-06 13:24:40

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

更好的包装:,您可以用一种更简单的方式编写它,将TextReaderLocation类型分离--通过这种方式,您可以轻松地独立地修改Location,甚至提供基于跟踪阅读进度的其他特性。

代码语言:javascript
复制
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对象报告进度:

代码语言:javascript
复制
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表达式甚至是反应性框架之类的特性。

简单的使用不会增加太多的锅炉板:

代码语言:javascript
复制
using(ReaderWithEvents reader = new ReaderWithEvents(source)) {
  reader.Advance += str => loc.AdvanceString(str);
  // ..
}

但是,您也可以使用反应性扩展来处理事件。要计算源文本中的新行数,可以编写如下内容:

代码语言:javascript
复制
var res =
  (from e in Observable.FromEvent(reader, "Advance")
   select e.Count(ch => ch == '\n')).Scan(0, (sum, current) => sum + current);

这将为您提供一个事件res,每次从TextReader读取字符串时都会触发该事件,该事件所携带的值将是当前行数(在文本中)。

票数 5
EN

Stack Overflow用户

发布于 2010-03-30 19:45:31

我倾向于使用普通的TextReader包装器,因为它似乎是最自然的方法。另外,我认为你提到的缺点并不是真正的缺点:

  • 除了他们的LocatedTextReaderWrapper实例之外,用户还需要创建和释放一个TextReader实例。

嗯,如果用户需要跟踪,他无论如何都需要操纵与跟踪相关的对象,创建一个包装器也不是什么大不了的事……为了简化工作,您可以创建一个扩展方法,为任何TextReader创建一个跟踪包装器。

  • 不允许在没有包装层的情况下添加不同类型的跟踪器或不同的位置跟踪器。

您可以始终使用Location对象的集合,而不是单个对象:

代码语言:javascript
复制
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.
}
票数 1
EN

Stack Overflow用户

发布于 2010-04-08 00:10:45

AFAIK,如果您使用PostSharp,它会将自己注入编译过程,并根据需要重写代码,这样您就不会有任何永久的依赖关系(除了要求API用户有PostSharp来编译您的源代码之外)。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/2548012

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档