gpt4 book ai didi

c# - 使用 Roslyn 在引用的程序集中获取接口(interface)实现

转载 作者:可可西里 更新时间:2023-11-01 07:57:39 25 4
gpt4 key购买 nike

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

所以,假设我定义了以下契约(Contract):

public interface IModule
{

}

这存在于说 Contracts.dll .

现在,如果我想发现这个接口(interface)的所有实现,我们可能会做类似以下的事情:
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 访问编译的所有引用。属性,我看不到如何获得任何有用的信息,例如可能访问字节码,可能加载程序集进行反射,或类似的信息。

    想法?

    最佳答案

    Thoughts?



    是的。

    通常在模块环境中,您希望根据上下文动态加载模块,或者 - 如果适用 - 从第三方加载。相比之下,使用 Roslyn 编译器框架,您基本上可以在编译时获得此信息,从而将模块限制为静态引用。

    就在昨天,我发布了动态加载工厂的代码。属性、加载 DLL 等的更新在这里: Naming convention for GoF Factory? .据我了解,它与您想要实现的目标非常相似。这种方法的好处是您可以在运行时动态加载新的 DLL。如果您尝试一下,您会发现它非常快。

    您还可以进一步限制您处理的程序集。例如,如果您不处理 mscorlibSystem.* (或者甚至可能是所有 GAC 程序集)它当然会工作得更快。不过,正如我所说,这应该不是问题。只是扫描类型和属性是一个相当快的过程。

    好的,更多信息和上下文。

    现在,您可能只是在寻找一个有趣的谜题。我可以理解,玩弄技术毕竟是一件很有趣的事情。下面的答案(马修本人)将为您提供所需的所有信息。

    如果您想平衡编译时代码生成与运行时解决方案的优缺点,这里有更多来自我的经验的信息。

    几年前,我认为拥有自己的 C# 解析器/生成器框架来进行 AST 转换是个好主意。这与您可以使用 Roslyn 所做的非常相似;基本上它将整个项目转换为 AST 树,然后您可以对其进行规范化、生成代码、对面向方面的编程内容进行额外检查并添加新的语言结构。我最初的目标是在 C# 中添加对面向方面编程的支持,为此我有一些实际应用。我会为你省去细节,但对于这种情况,可以说基于代码生成的模块/工厂也是我尝试过的事情之一。

    性能、灵活性和代码量(在非库解决方案中)是我权衡运行时和编译时决策的关键方面。让我们分解它们:
  • 表现。这很重要,因为我不能假设库代码不在关键路径上。运行时每个 appdomain 实例将花费您几毫秒。 (有关如何/为什么的评论,请参见下文)。
  • 灵活性。它们在属性/扫描方面都同样灵活。但是,在运行时,您在更改规则方面有更多的可能性(例如,动态插入模块等)。我有时会使用它,特别是基于配置,这样我就不必在同一个解决方案中开发所有内容(因为那样效率低下)。
  • 代码量。根据经验,更少的代码通常是更好的代码。如果你做对了,两者都会产生一个你需要在类上的属性。换句话说,这两种解决方案在这里给出了相同的结果。

  • 不过,关于性能的说明是有序的。我在我的代码中使用反射不仅仅是工厂模式。我在这里基本上有一个广泛的“工具”库,其中包括所有设计模式(以及大量其他东西)。几个例子:我在运行时自动为工厂、责任链、装饰器、模拟、缓存/代理(等等)生成代码。其中一些已经要求我扫描程序集。

    作为一个简单的经验法则,我总是使用一个属性来表示必须更改某些内容。您可以利用它来发挥自己的优势:通过简单地将每个类型的属性(正确的程序集/命名空间)存储在某个地方的单例/字典中,您可以使应用程序更快(因为您只需要扫描一次)。从 Microsoft 扫描程序集也不是很有用。我对大型项目进行了大量测试,发现在我发现的最坏情况下,扫描使应用程序的启动时间增加了大约 10 毫秒。请注意,这对于 appdomain 的每次实例化仅发生一次,这意味着您甚至永远不会注意到它。

    类型的激活实际上是您将获得的唯一“真实”性能损失。可以通过发出 IL 代码来优化这种惩罚;这真的没有那么难。最终结果是它不会在这里产生任何区别。

    总结一下,这是我的结论:
  • 性能:差别不大。
  • 灵活性:运行时胜出。
  • 代码量:差别不大。

  • From my experience, although a lot of frameworks hope to support plug and play architectures which could benefit from drop in assemblies, the reality is, there isn't a whole load of use-cases where this is actually applicable.



    如果它不适用,您可能首先要考虑不使用工厂模式。此外,如果它适用,我已经表明它没有真正的缺点,即:如果您正确实现它。不幸的是,我必须在这里承认,我已经看到了很多糟糕的实现。

    至于它实际上并不适用的事实,我认为这只是部分正确。插入数据提供程序很常见(它在逻辑上遵循 3 层架构)。我还使用工厂来连接诸如通信/WCF API、缓存提供程序和装饰器(逻辑上遵循 n 层架构)之类的东西。一般来说,它用于您能想到的任何类型的提供者。

    如果论点是它会带来性能损失,那么您基本上希望删除整个类型扫描过程。就我个人而言,我将它用于大量不同的事情,最显着的是缓存、统计、日志记录和配置。此外,我认为性能下降可以忽略不计。

    只是我的 2 美分;哈。

    关于c# - 使用 Roslyn 在引用的程序集中获取接口(interface)实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31119284/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com