使用 Roslyn 获取引用程序集中的接口实现

2024-01-09

我想在我正在开发的框架中绕过一些经典的程序集扫描技术。

因此,假设我定义了以下合同:

public interface IModule
{

}

这存在于说Contracts.dll.

现在,如果我想发现该接口的所有实现,我们可能会执行类似于以下的操作:

public IEnumerable<IModule> DiscoverModules()
{
    var contractType = typeof(IModule);
    var assemblies = AppDomain.Current.GetAssemblies() // Bad but will do
    var types = assemblies
        .SelectMany(a => a.GetExportedTypes)
        .Where(t => contractType.IsAssignableFrom(t))
        .ToList();

    return types.Select(t => Activator.CreateInstance(t));
}

这不是一个很好的例子,但它可以。

现在,这些类型的程序集扫描技术的性能可能非常低下,而且都是在运行时完成的,通常会影响启动性能。

在新的DNX环境中,我们可以使用ICompileModule实例作为元编程工具,因此您可以捆绑一个实现ICompileModule进入你的Compiler\Preprocess文件夹并让它做一些时髦的事情。

我的目标是使用ICompileModule实现,以完成我们在运行时执行的工作,而不是在编译时执行。

  • 在我的参考文献(编译和程序集)和我当前的编译中,发现所有可实例化的实例IModule
  • 创建一个类,我们调用它ModuleList具有生成每个模块的实例的实现。
public static class ModuleList
{
    public static IEnumerable<IModule>() GetModules()
    {
        yield return new Module1();
        yield return new Module2();
    }
}

将该类添加到编译单元后,我们可以调用它并在运行时获取模块的静态列表,而不必搜索所有附加的程序集。我们本质上是在卸载编译器而不是运行时的工作。

鉴于我们可以通过以下方式访问编译的所有参考文献References属性,我看不到如何获取任何有用的信息,例如可能访问字节代码,可能加载用于反射的程序集,或类似的信息。

想法?


想法?

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。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 Roslyn 获取引用程序集中的接口实现 的相关文章

随机推荐

  • 截取 BlackBerry 中当前屏幕的屏幕截图

    是否可以在 BlackBerry 应用程序中截取当前屏幕的屏幕截图 我的屏幕上有一个按钮 当我单击该按钮时 我想发送一封包含当前屏幕截图的电子邮件 您可以使用以下方法获取显示屏幕的屏幕截图 public static Bitmap getS
  • mvc 3会话和authorizeAttribute

    我的网站对所有人开放 但我有一个控制器 其中有某种方法 只有具有用户名和密码的管理员才能输入 我正在拯救布尔IsManager in a session 我想使用授权属性来阻止任何人IsManager false 首先定义一个ActionF
  • 以编程方式导航时,Angular Router 旧组件保留在 dom 中

    角度版本 4 从登录后LoginComponent 位于public signin路线 我想导航到TestComponent在路径中public test 我这样做如下 this router navigate public test 然后
  • CSS - 三个可滚动等高主要内容列(流体高度)和粘性/始终可见的页脚

    我几乎浏览了这里和 Google 上的所有帖子 似乎找不到关于 3 个等高列 带有 真正 粘性页脚 的 CSS 布局问题的答案 布局要求如下 顶部标题部分 其中一行用于下拉菜单选项 另一行用于工具栏按钮 主要内容部分 有 3 个等高的列 中
  • Javascript 相当于 Python 的 locals() 吗?

    在Python中 我们可以使用内置函数获取当前范围内所有局部和全局变量的字典locals and globals 在 Javascript 中是否有一些等效的方法可以做到这一点 例如 我想做如下的事情 var foo function al
  • 使用值和引用参数类型重载的方法

    我有以下代码 class Calculator public int Sum int x int y return x y public int Sum out int x out int y x y 10 return x y class
  • d3:画笔更改单击鼠标悬停

    我正在尝试结合画笔选择 工具提示和单击事件来创建散点图 但似乎一旦我将画笔添加到 svg 画布 对象上的所有单击事件都会映射到鼠标悬停 有没有办法解决 下面的示例代码和 http jsfiddle net 7j8cr http jsfidd
  • 端口 7071 不可用。关闭使用该端口的进程,或使用 --port [-p] 指定另一个端口

    我尝试从本地运行 azure 函数应用程序 Http Triggerd API 使用 VS 代码 但我收到错误 端口 7071 不可用 使用该端口关闭进程 或使用 port p 指定另一个端口 我使用cmd提示符检查了使用的端口列表 但是7
  • 如何将 NSUInteger 转换为 NSString?

    你如何转换NSUInteger进入一个NSString 我已经尝试过但是我的NSString一直返回0 NSUInteger NamesCategoriesNSArrayCount self NamesCategoriesNSArray c
  • 从命令提示符或 PowerShell 调用 MSYS2 Shell

    从命令提示符或 PowerShell 开始 两者都可以 我可以弄清楚如何执行另一个给定的操作 如何调用 MSYS2 shell 来运行命令 更具体的问题 我包含此信息是为了避免 XY 问题 但我坚信最好通过回答上述 更一般的 问题来解决这个
  • 外部函数调用不同的包注意

    我正在准备一个 R 包以提交给 CRAN R CMD 检查给了我以下注释 外部函数调用不同的包 Fortran cinc 包 cmprsk Fortran crstm PACKAGE cmprsk 看 Writing R 的 系统和外语界面
  • 从 C# LINQ 解析 XML 时如何保留空白字符

    我需要在 C 代码或 XML 文档中做什么 以便 XDocument 解析器读取文字空白Values of XElements Background 我有一个 XML 文档 其中一部分如下所示
  • $settings 数组或 Config 类来存储项目设置?

    我应该如何存储项目设置 哪个更好 使用 settings包含我所有设置的数组 settings max photos 30 或创建一个单例Config类中包含所有设置 Class Config private max photos 30 有
  • linux top 显示java线程?

    在Linux服务器 fedora 中 我们运行单个JBOSS应用程序服务器 并使用quartz来调度我们的任务 昨天 我使用 top 命令来查看进程状态 查看多个名为Java显示不同的pid 但是如果我使用 ps aux grep java
  • 在滚动条到达底部之前执行 100px 的操作

    我有下面的 Javascript 当滚动条到达页面底部时 警报就会按预期显示 但是 我希望这发生在 100 像素处before它到达底部 我该怎么做呢 window scroll function if window scrollTop d
  • Glide:如何查找图像是否已缓存并使用缓存版本?

    Scenario 我有一个大的 GIF 图像 我想在用户第一次使用它打开应用程序时缓存该图像Glide https github com bumptech glide 图像加载和缓存库 之后 每当用户打开应用程序时 我想显示缓存的版本 如果
  • 核心数据:如何将自定义对象存储为可转换属性?

    目前我有一个名为 Place 的类 定义如下 class Place let name String let address String let coordinate CLLocationCoordinate2D let type Str
  • 使用 jquery 将解析后的文本转换为整数

    我当前正在解析一个 XML 文件 其中一个字段是一个整数 用于确定对象的大小 xml find Boxes each function var top box find top text 然后用 HTML 显示它 var html div
  • 使用增量主键sql插入多行

    INSERT INTO TABLE1 COLUMN1 PRIMARY KEY SELECT COLUMN1 SELECT COALESCE MAX PRIMARY KEY 0 FROM TABLE1 1 FROM TABLE2 error
  • 使用 Roslyn 获取引用程序集中的接口实现

    我想在我正在开发的框架中绕过一些经典的程序集扫描技术 因此 假设我定义了以下合同 public interface IModule 这存在于说Contracts dll 现在 如果我想发现该接口的所有实现 我们可能会执行类似于以下的操作 p