为什么我需要在运行时编译/生成方法/代码

本文关键字:方法 代码 编译 运行时 为什么 | 更新日期: 2023-09-27 18:35:42

我对理论方面感兴趣。在典型的日常编程场景中,在 C# 中使用 Codedom/反射或 Lambda 表达式/表达式树在运行时生成代码需要什么?不在特殊情况下

或者在 Ruby 等其他语言中,为什么要在运行时使用 monkeypatching 添加/修改类?

这怎么可能是一个优势?

为什么我需要在运行时编译/生成方法/代码

在 Ruby 中,生成方法的方法无处不在

例如,有用于生成二传手和吸气手方法的方法:

class Module
  def attr_reader(*attributes)
    attributes.each do |attribute|
      define_method(attribute) do instance_variable_get(:"@#{attribute}") end
    end
  end
  def attr_writer(*attributes)
    attributes.each do |attribute|
      define_method(:"#{attribute}=") do |value|
        instance_variable_set(:"@#{attribute}", value)
      end
    end
  end
  def attr_accessor(*attributes)
    attr_reader(*attributes)
    attr_writer(*attributes)
  end
end

当我像这样调用这些方法之一时:

attr_reader :bla

这将生成一个名为 bla 的方法,该方法返回名为 @bla 的实例变量的值。

attr_writer :bla

将生成一个名为 bla= 的方法(在末尾用等号命名方法是 Ruby 中 setter 的常用命名约定),该方法将其参数分配给名为 @bla 的实例变量。

class Foo
  attr_reader   :foo,    :bar
  attr_writer   :baz,    :quux
  attr_accessor :gargle, :blarf
end

这将生成方法foobarbaz=quux=garglegargle=blarfblarf=

另一个例子是 Ruby 的 ActiveRecord ORM 库,它基于运行时对数据库模式的反射生成 getter 和 setter 方法以及模型的查找器方法。

ActiveRecord还具有动态查找器方法,可让您执行类似 Person.find_by_name_and_age('John', 42) .当然,人们可能想要搜索无限多个数据库列的组合,因此提前生成所有方法是不切实际的。相反,ActiveRecord安装一个method_missing钩子来拦截对以 find_by_ 开头的非现有方法的调用。但是,一旦它拦截了方法调用,它就会生成该名称的方法,前提是该方法可能会在某个时间更频繁地调用,因此可以避开动态拦截。

一般来说,这种代码合成代码生成有用的任何地方都很有用,但具有显着的优势

  • 它不会弄乱你的源代码树,因为所有生成的"代码"都是短暂的
  • 你正在使用语言自己的工具来生成代码,所以它(在某种意义上)总是正确的,而对于(简单的文本)代码生成,你必须注意生成语法上有效的代码。

我遇到了几个案例。一种情况是出于性能原因。例如,当您存储创建对象的已编译委托时。例如一些 IoC 框架。这些已编译的委托创建速度往往很慢,但如果多次调用同一个委托,执行速度非常快,则可以提供良好的性能改进。

我遇到的另一种情况是使用谓词构建动态过滤器,然后在数据库中序列化这些过滤器,以便用户不需要再次定义它们。

有时你必须牺牲完全干净的代码来获得大量的生产力和革命性。

例如,如果您开发了 1000 个类并为每个类创建一个二进制序列化程序。 通过反射和代码生成真的很容易做到。维护唯一的动态代码比维护大量静态代码的难度要小。在对动态代码进行全面鉴定后,风险确实最小化了。

幸运的是,通用涵盖了这个问题的很大一部分。

运行时生成 msil 在处理一个小项目时是无用的。

另一个考虑因素是水平开发(公用事业/助手)不依赖于直接业务发展。