动态语言运行时与反射

41

我计划在我的新项目中使用动态关键字。但是在开始之前,我想了解使用动态关键字而不是反射的优缺点。

以下是我在使用动态关键字方面找到的优点:

  • 易读易维护的代码。
  • 代码行数更少。

然而,使用动态关键字可能带来的负面影响包括:

  • 影响应用程序性能。
  • 动态关键字在内部是反射的一个包装器。
  • 动态类型可能会成为难以发现错误的温床。
  • 影响与之前的.NET版本的互操作性。

请帮我确认一下我找到的优缺点是否合理?


你需要进行私有反射吗? - Eric Lippert
不,我不需要进行私有反射。无论我需要调用什么方法,它们总是公开的。 - AbrahamJP
6个回答

107
请帮我判断我发现的利弊是否合理? 我对您提出的利弊之一感到担忧,因为其中一些并未涉及使用反射和使用动态之间的差异。任何动态类型系统都存在运行时无法捕获的错误,这是动态语言的缺陷。反射代码与使用动态类型的代码一样容易出现错误。 不要从利弊的角度来考虑它,更中立地思考。我想问的问题是“使用反射和使用动态类型之间有什么区别?” 第一:使用反射可以精确获取所需内容,而使用动态类型则是获取C#编译器在编译时给定类型信息后会执行的内容。这两个内容可能是完全不同的。如果您拥有指向特定方法的MethodInfo,并使用特定参数调用该方法,则会调用该方法,无论如何。如果您使用“dynamic”,则是在运行时请求DLR确定C#编译器关于调用哪个方法的正确意见。 C#编译器可能选择与您实际想要的方法不同的方法。 第二:使用反射可以(如果您的代码被授予适当的高级别信任)进行私有反射。您可以调用私有方法,读取私有字段等。是否这样做是个好主意,我不知道。对我来说,它似乎很危险和愚蠢,但我不知道您的应用程序是什么。使用动态类型则会返回C#编译器的行为。私有方法和字段不可见。

第三点:使用反射时,你编写的代码看起来像一个机制。看起来你正在加载元数据源,提取一些类型、提取一些方法信息,并通过方法信息在接收对象上调用方法。每一步都像一个机制的操作。使用动态语言,每一步看起来像是业务逻辑。你以与其他任何代码相同的方式在接收器上调用方法。什么是重要的?在某些代码中,机制实际上是最重要的事情。在某些代码中,机制实现的业务逻辑是最重要的事情。选择强调正确抽象级别的技术。

第四点:性能成本是不同的。使用反射,您不会得到任何缓存行为,这意味着操作通常较慢,但不会有维护缓存的内存成本,每个操作的成本大致相同。使用DLR,第一次操作确实非常慢,因为它进行了大量分析,但分析被缓存和重复使用。这消耗了内存,换取某些场景下后续调用的速度增加。对于您的应用程序来说,速度和内存使用的正确平衡是什么,我不知道。


感谢对我的问题进行详细分析。很高兴知道DLR会缓存分析结果。 - AbrahamJP
2
这绝对是对这个问题最好的答案。 - Keith Jackson

27

可读性好、易于维护的代码

从我的经验来看,这是确实的。

代码行数更少。

影响不大,但有所帮助。

影响应用程序的性能。

影响非常小,但与反射方式相比仍有很大差距。

动态关键字在内部是 Reflection 的包装器。

完全不正确。动态关键字利用了 Dynamic Library Runtime。

[编辑:根据下面的评论进行更正]

似乎 Dynamic Language Runtime 确实使用了反射,而性能改进只是由于缓存技术造成的。

动态数据类型可能会成为难以找到错误的温床。

这可能是真的;这取决于你如何编写你的代码。你实际上正在从你的代码中删除编译器检查。如果你的测试覆盖率很好,这可能不重要;如果不是,我认为你会遇到问题。

影响与之前的 .NET 版本的互操作性

不正确。我的意思是,你将无法针对旧版本编译你的代码,但如果你想这样做,那么你应该以旧版本为基础,并进行向上编译而不是相反。但如果你想使用 .NET 2 库,那么你不应该遇到太多问题,只要在 app.config / web.config 中包含声明即可。

你所忽略的一个显著优点是与 COM/ATL 组件的改进互操作性。


所以我可以相信您的话,使用动态语言运行库(DLR)不会比反射产生任何明显的性能差异。感谢指出COM\ATL上改进的互操作性。 - AbrahamJP
1
@AbrahamJP - 使用动态方式比使用反射更快,特别是如果一个方法被重复调用(因为有缓存)。 - pdr
25
“完全不是真的。dynamic关键字利用动态运行时库。” 你认为DLR如何做到的?它有什么魔力可以在不使用反射的情况下进行运行时类型分析? DLR没有任何魔力;它像其他代码一样,在需要时使用反射。 它只是非常聪明地缓存结果,但请相信我,它使用了反射。 - Eric Lippert
2
@Eric Lippert - 我改口了。我理解这比那复杂一些。 - pdr
8
比那个复杂得多!并不是所有的 DLR 分析提供程序都使用反射,但 C# 的一个肯定会广泛地使用反射来执行运行时类型分析。即使结果被缓存,作为缓存键的运行时类型也总是通过反射确定的。 - Eric Lippert

16

动态和反射之间有四个很大的区别。以下是详细的解释。参考http://www.codeproject.com/Articles/593881/What-is-the-difference-between-Reflection-and-Dyna

点1. 检查 VS 调用

反射可以做两件事情,一是检查元数据,二是在运行时调用方法。而在动态中我们只能调用方法。如果我正在创建像Visual Studio IDE这样的软件,那么反射是最好的选择。如果我只想从我的C#代码中进行动态调用,则动态是最好的选择。

DynamicVsReflection

点2. 私有 Vs 公共 方法调用

使用动态无法调用私有方法。在反射中可以调用私有方法。

点3. 缓存

动态在内部使用反射,并且它还提供了缓存的好处。所以,如果你只想动态调用对象,那么动态是最好的选择,因为它会带来性能上的优势。

点4. 静态类

动态是针对实例的:您无法访问静态成员;在这些情况下,您必须使用反射。


4
在大多数情况下,使用动态关键字不会显著缩短代码。在某些情况下,它可能会;这取决于提供程序,因此这是一个重要的区别。您可能永远不应该使用动态关键字来访问纯CLR对象;那里的好处太小了。
动态关键字削弱了自动重构工具,并使高覆盖率单元测试更加重要;毕竟,当您使用它时,编译器并没有检查太多东西。当您与非常稳定或固有动态类型API进行交互时,这不是太大的问题,但如果您使用关键字dynamic来访问其API可能会在将来发生更改的库(例如您自己编写的任何代码),那么这是特别恶心的。
适度使用关键字,只在有意义的地方使用,并确保这样的代码具有充足的单元测试。不要在不需要或可以使用类型推断(例如var)的地方使用它。
编辑:您在下面提到正在为插件执行此操作。设计托管可扩展性框架就是考虑到这一点的-它可能是关键字dynamic和反射的更好选择。

1
@Eamon:如我所提到的,我没有使用动态关键字来访问纯CLR对象,而是使用了我提到的应用程序,提供插件模型来处理新功能,因此反射被广泛使用。为了使这个模块有效和可维护,我对选择使用反射还是动态关键字感到困惑。对我来说,主要问题是性能。 - AbrahamJP
2
@AbrahamJP 你的插件模型是通过接口还是约定来支持的?如果是通过接口支持的话,那么就不需要使用太多反射了。 - Tim Lloyd
插件不应该需要动态类型。是否存在性能问题实际上取决于你正在做什么(数百万个小调用与一两个大调用),但是我不会事先期望性能是个问题。 - Eamon Nerbonne
@Eamon:对于插件模型,我们使用接口,而 XML 文件用于处理一些异常情况。调用在 XML 文件中定义的方法是复杂的部分。我们动态创建委托来构建函数容器。 - AbrahamJP
@Eamon:感谢您指出托管可扩展框架,我一定会去了解它。 - AbrahamJP

2

如果您使用动态编程来进行反射,那么您唯一需要考虑的是与先前版本的兼容性。否则,它比反射更易读且更短。无论如何,从使用反射开始,您都会失去强类型和(某些)性能。


1

就我的看法,在使用动态语言时,你提到的所有缺点,除了与旧版 .NET 兼容性之外,使用反射也存在:

影响应用程序性能

虽然它确实会影响性能,但使用反射也是如此。据我所记,DLR 在首次访问给定类型的动态对象的方法/属性时,更多或更少地使用反射,并缓存类型/访问目标对,以便以后的访问只是在缓存中进行查找,使其比反射更快。

Dynamic 关键字在内部是 Reflection 的包装器

即使这是真的(见上文),那也有什么负面影响呢?无论它是否包装了 Reflection 都不应对你的应用程序产生任何重大影响。

动态类型可能成为难以发现错误的滋生场所

虽然这是真的,但只要你谨慎使用,它就不应该成为太大的问题。此外,如果你基本上将其作为反射的替代品使用(也就是说,你只在最短的时间内使用 dynamic 来通过反射访问某些东西),那么这种错误的风险不应该比使用反射访问方法/属性时显著更高(当然,如果你把所有东西都变成动态的,可能会更成问题)。

影响与先前的.NET版本的互操作性。 对此,您必须自己决定这对您有多大的影响。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接