想法?
Yes.
通常,在模块环境中,您希望根据上下文或(如果适用)从第三方动态加载模块。相比之下,使用 Roslyn 编译器框架,您基本上可以在编译时获取此信息,从而将模块限制为静态引用。
就在昨天,我发布了动态加载工厂的代码。属性、加载 DLL 的更新等:GoF Factory 的命名约定? https://stackoverflow.com/questions/31263041/naming-convention-for-gof-factory?noredirect=1#comment50527877_31263041。据我了解,这与您想要实现的目标非常相似。该方法的优点是您可以在运行时动态加载新的 DLL。如果你尝试一下,你会发现它相当快。
您还可以进一步限制您处理的程序集。例如,如果您不处理mscorlib
and System.*
(或者甚至可能是所有 GAC 组件)当然它的工作速度会快得多。不过,正如我所说,这不应该是一个问题;仅扫描类型和属性是一个相当快的过程。
好的,更多信息和背景。
现在,您可能只是在寻找有趣的谜题。我能理解,玩弄技术毕竟很有趣。下面的答案(马修本人)将为您提供您需要的所有信息。
如果您想平衡编译时代码生成与运行时解决方案的优缺点,这里有来自我的经验的更多信息。
几年前,我认为拥有自己的 C# 解析器/生成器框架来执行 AST 转换是个好主意。这与您对 Roslyn 所做的非常相似;基本上,它将整个项目转换为 AST 树,然后您可以对其进行规范化、生成代码、对面向方面的编程内容进行额外检查并添加新的语言结构。我最初的目标是在 C# 中添加对面向方面编程的支持,为此我有一些实际应用。我不会向您介绍细节,但对于这种情况,足以说基于代码生成的模块/工厂也是我尝试过的事情之一。
性能、灵活性和代码量(在非库解决方案中)是我在运行时和编译时决策之间权衡决策的关键方面。让我们把它们分解一下:
-
表现。这很重要,因为我不能假设库代码不在关键路径上。每个 appdomain 实例的运行时间将花费几毫秒。 (有关如何/为什么的说明,请参阅下文)。
-
灵活性。它们在属性/扫描方面都具有同样的灵活性。但是,在运行时,您有更多更改规则的可能性(例如动态插入模块等)。我有时会使用它,特别是基于配置,这样我就不必在同一个解决方案中开发所有内容(因为那样效率低下)。
-
代码量。根据经验,更少的代码通常是更好的代码。如果你做得正确,两者都会产生你在类上需要的单个属性。换句话说,两种解决方案在这里给出相同的结果。
不过,关于性能的说明是有必要的。我在代码中使用反射不仅仅是工厂模式。我基本上有一个广泛的“工具”库,其中包括所有设计模式(以及大量其他内容)。举几个例子:我在运行时自动为工厂、责任链、装饰器、模拟、缓存/代理(等等)等内容生成代码。其中一些已经要求我扫描程序集。
作为一个简单的经验法则,我总是使用属性来表示必须更改某些内容。您可以利用这一点来发挥您的优势:通过简单地将每种类型的属性(正确的程序集/命名空间)存储在单例/字典中的某个位置,您可以使应用程序更快(因为您只需要扫描一次)。从 Microsoft 扫描程序集也不是很有用。我对大型项目做了很多测试,发现在最坏的情况下,扫描使应用程序的启动时间增加了大约 10 毫秒。请注意,每次应用程序域实例化仅执行一次,这意味着您甚至不会注意到它。
类型的激活实际上是您将得到的唯一“真正的”性能损失。可以通过发出 IL 代码来优化该损失;这真的没那么难。最终的结果是,这里不会有任何区别。
总结一下,这是我的结论:
-
表现: 差别不大。
-
灵活性:运行时获胜。
-
代码量: 差别不大。
根据我的经验,尽管许多框架希望支持即插即用架构,这可以从程序集中的删除中受益,但现实是,并没有大量的用例真正适用。
如果它不适用,您可能首先要考虑不使用工厂模式。另外,如果它适用的话,我已经证明它没有真正的缺点,那就是:如果你正确地实现它。不幸的是,我必须承认,我已经看到了很多糟糕的实现。
至于它实际上并不适用,我认为这只是部分正确。插入式数据提供者很常见(逻辑上它遵循 3 层架构)。我还使用工厂来连接诸如通信/WCF API、缓存提供程序和装饰器之类的东西(逻辑上遵循 n 层架构)。一般来说,它可用于您能想到的任何类型的提供商。
如果争论是它会带来性能损失,那么您基本上希望删除整个类型扫描过程。就我个人而言,我将其用于很多不同的事情,最显着的是缓存、统计、日志记录和配置。另外,我认为性能下降可以忽略不计。
只是我的2分钱; HTH。