1

从一开始,我正在开发一个实用程序来解析和审查来自第三方应用程序的日志,并明确要求极高的灵活性。

为了实现这一点,我正在充分发挥 LINQ 的潜力(可能还不止于此:/)。

由于用户应该通过实用程序的 UI 构建自己的查询,因此我需要能够动态加载和卸载它们。为此,我正在使用一个方法(字面意思为“DoStuff”)构建虚拟类,该方法返回查询结果(通常基于 LINQ,但可能存在例外),将它们(通过 CSharpCodeProvider)编译为临时程序集,将它们加载到“一次性”AppDomain 上,运行查询,然后摆脱 AppDomain 和程序集。

这就是警报触发的地方:LINQ + AppDomains = 很多出现问题的机会。

更糟糕的是,要确定每个域上将运行什么以及跨越(或试图跨越)域边界的内容可能会变得非常棘手。我希望 stackoverflow 中的知识库可以帮助解决这个问题。

因此,深入细节,这是我所拥有的简化版本:

在主程序集(实用程序的实际 .exe 文件)上,它只会由执行它的用户以“正常方式”加载,我有类似这样的东西:

public abstract class LogEntry { // Should this inherit from MarshalByRefObject?
    protected internal abstract void Parse(StreamReader input);
    ...
    /* There are literally dozens of classes that inherit from LogEntry, dealing with different kinds of entries, and
     *   defining an appropriate implementation of Parse().
     * The logic to choose the appropriate sub-class would be within LogData's constructor (see below).
     */
}
internal class LogData: IEnumerable<LogEntry> { // Should this inherit from MarshalByRefObject?
    private List<LogEntry> loadedData = new List<LogEntry>();
    public LogData(IEnumerable<string> LogFiles) { ... }
    /* The enumerator for this class does some heavy-dutty work to find the log files and load them "as needed":
     * Each time a new entry is retrieved from the logs, it's saved on loadedData just before returning it;
     * When the enumerator is reset and used again, it goes through loadedData until exhausting it, and only then
     * goes back into actually loading (and saving) new data.
     * I'm not including the code here because it is ugly, verbose, and probably irrelevant to the question.
     */
}

(除了一些形式,样板启动代码等)。该程序将要求用户提供一组日志文件(使用 FileOpenDialogs、支持带有经典“*”和“?”通配符的文件名模式的文本字段等),并使用它们来初始化 LogData 的单个实例.

之后,用户会看到一个闪亮的 GUI 来构建和微调查询,包括一个 Doom's Day 按钮来运行查询。这就是乐趣的开始。这个想法是生成代码来表示用户的查询,然后将其包装成这样的东西(我在这里省略了“使用”语句和其他内容):

public class whatever { // I'm thinking of making this static and saving on the constructor invokation work, but that's a side topic.
    public IEnumerable DoStuff(IEnumerable<LogEntry> Input) {
        // This will be filled with code created on-the-fly based on user input
        ...
    }
}

然后将其提供给 CSharpCodeProvider 以将其(使用预定义的、可配置的引用列表)编译为临时程序集。然后我将程序集加载到“丢弃”AppDomain,创建“whatever”实例,调用它的 DoStuff 方法(将我的 LogData 实例传递给它),检索结果,卸载域,并摆脱组件。

现在,幕后会发生什么?这才是真正的问题^^。跟踪每个域上运行的代码以及在它们之间来回反弹的数据是相当棘手的。经过一些研究,我正在做出一些有根据的猜测,所以如果有人能告诉我我是否猜对了就足够了:

1,LogData 必须从 MarshalByRefObject 继承,因此当它传递给用户的查询 (DoStuff) 时,枚举器仍然在“默认”应用程序域上执行并且一切正常。

2,) 上面的意思是,没有任何 LogData 实例跨越 AppDomain 边界(对其成员的调用是另一回事),但它的条目(LogEntry 实例)在从 DoStuff 迭代日志时会发生。那么,那些应该是可序列化的吗?它们根本不难反序列化,尽管我觉得这就像将解析工作加倍。将它们改为 MarshalByRefObject 后代会起作用吗?

3,无论DoStuff返回什么,都需要复制,而不是引用,所以在卸载“扔掉”后仍然可以从主AppDomain使用它。因此,查询可能包含在其结果中的任何类型都需要可序列化,而不是 MarshalByRefObject。

4,即使 DoStuff 返回一个 LINQ 查询(类似于“从输入中的条目返回...”),查询的枚举器也将仅在“丢弃”域上运行,并且仅在单个项目(其中,在上面一点,我已经说过那些无论如何都应该是可序列化的)实际上会跨越域边界。

这些结论对吗?我错过了什么吗?

提前感谢您的任何回答。

PS:基于丹尼尔在下面的评论,我想我必须对项目的某些方面进行更深入的了解:

  1. 虽然“查询构建器”旨在成为定义查询的主要机制,但该程序还将让用户在构建器不够用时引入原始代码(以及对于那些对编程足够熟悉而更愿意键入一些代码的用户)围绕下拉菜单、菜单和其他花哨的东西导航的代码;这恰好是项目目标受众的重要组成部分)。

  2. 我没有提到的另一个功能是它可以保存查询以供以后使用。对于由 C# 代码表示的查询,持久化它们是微不足道的(在编译之前,它们只是字符串)。持久化的表达式树(或任何类似的东西)很快就会变成一个大障碍。

  3. 这不是我第一次潜入CodeProvider课堂。动态加载程序集;也不是 LINQ。但这可能是我第一次如此深入地研究这个;绝对是我第一次尝试将所有这些放在一起。我熟悉这些功能中的每一个的潜力,并且相当有信心,一旦理清了有关它们交互的棘手细节,我正在尝试的事情将相对容易。这个问题只是关于那些细节,我可以从那里处理其他所有事情。

4

0 回答 0