为什么我需要在运行时编译/生成方法/代码
本文关键字:方法 代码 编译 运行时 为什么 | 更新日期: 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
这将生成方法foo
、bar
、baz=
、quux=
、gargle
、gargle=
、blarf
、blarf=
。
另一个例子是 Ruby 的 ActiveRecord
ORM 库,它基于运行时对数据库模式的反射生成 getter 和 setter 方法以及模型的查找器方法。
ActiveRecord
还具有动态查找器方法,可让您执行类似 Person.find_by_name_and_age('John', 42)
.当然,人们可能想要搜索无限多个数据库列的组合,因此提前生成所有方法是不切实际的。相反,ActiveRecord
安装一个method_missing
钩子来拦截对以 find_by_
开头的非现有方法的调用。但是,一旦它拦截了方法调用,它就会生成该名称的方法,前提是该方法可能会在某个时间更频繁地调用,因此可以避开动态拦截。
一般来说,这种代码合成在代码生成有用的任何地方都很有用,但具有显着的优势
- 它不会弄乱你的源代码树,因为所有生成的"代码"都是短暂的
- 你正在使用语言自己的工具来生成代码,所以它(在某种意义上)总是正确的,而对于(简单的文本)代码生成,你必须注意生成语法上有效的代码。
我遇到了几个案例。一种情况是出于性能原因。例如,当您存储创建对象的已编译委托时。例如一些 IoC 框架。这些已编译的委托创建速度往往很慢,但如果多次调用同一个委托,执行速度非常快,则可以提供良好的性能改进。
我遇到的另一种情况是使用谓词构建动态过滤器,然后在数据库中序列化这些过滤器,以便用户不需要再次定义它们。
有时你必须牺牲完全干净的代码来获得大量的生产力和革命性。
例如,如果您开发了 1000 个类并为每个类创建一个二进制序列化程序。 通过反射和代码生成真的很容易做到。维护唯一的动态代码比维护大量静态代码的难度要小。在对动态代码进行全面鉴定后,风险确实最小化了。
幸运的是,通用涵盖了这个问题的很大一部分。
在运行时生成 msil 在处理一个小项目时是无用的。
另一个考虑因素是水平开发(公用事业/助手)不依赖于直接业务发展。