如何在Ruby中"伪造"C#​​样式属性?

Sam*_*ron 5 ruby attributes

编辑:我稍微改变了规格,以更好地匹配我想象的那样做.

好吧,我真的不想伪造C#属性,我想要一次性并支持AOP.

鉴于该计划:

class Object
  def Object.profile 
    # magic code here
  end
end 

class Foo
  # This is the fake attribute, it profiles a single method.
  profile
  def bar(b)
   puts b
  end

  def barbar(b)
    puts(b)
  end

  comment("this really should be fixed")
  def snafu(b)
  end

end

Foo.new.bar("test")
Foo.new.barbar("test") 
puts Foo.get_comment(:snafu)
Run Code Online (Sandbox Code Playgroud)

期望的输出:

Foo.bar was called with param: b = "test"
test
Foo.bar call finished, duration was 1ms
test
This really should be fixed

有没有办法实现这个目标?

Yeh*_*atz 11

我有一个不同的方法:

class Object
  def self.profile(method_name)
    return_value = nil
    time = Benchmark.measure do
      return_value = yield
    end

    puts "#{method_name} finished in #{time.real}"
    return_value
  end
end

require "benchmark"

module Profiler
  def method_added(name)
    profile_method(name) if @method_profiled
    super
  end

  def profile_method(method_name)
    @method_profiled = nil
    alias_method "unprofiled_#{method_name}", method_name
    class_eval <<-ruby_eval
      def #{method_name}(*args, &blk)
        name = "\#{self.class}##{method_name}"
        msg = "\#{name} was called with \#{args.inspect}"
        msg << " and a block" if block_given?
        puts msg

        Object.profile(name) { unprofiled_#{method_name}(*args, &blk) }
      end
    ruby_eval
  end

  def profile
    @method_profiled = true
  end
end

module Comment
  def method_added(name)
    comment_method(name) if @method_commented
    super
  end

  def comment_method(method_name)
    comment = @method_commented
    @method_commented = nil
    alias_method "uncommented_#{method_name}", method_name
    class_eval <<-ruby_eval
      def #{method_name}(*args, &blk)
        puts #{comment.inspect}
        uncommented_#{method_name}(*args, &blk)
      end
    ruby_eval
  end

  def comment(text)
    @method_commented = text
  end
end

class Foo
  extend Profiler
  extend Comment

  # This is the fake attribute, it profiles a single method.
  profile
  def bar(b)
   puts b
  end

  def barbar(b)
    puts(b)
  end

  comment("this really should be fixed")
  def snafu(b)
  end
end
Run Code Online (Sandbox Code Playgroud)

关于这个解决方案的几点:

  • 我通过模块提供了其他方法,可以根据需要扩展到新类.这可以避免污染所有模块的全局命名空间.
  • 我避免使用alias_method,因为模块包含允许AOP样式的扩展(在本例中为for method_added),而不需要别名.
  • 我选择使用class_eval而不是define_method定义新方法,以便能够支持带块的方法.这也需要使用alias_method.
  • 因为我选择支持块,所以在方法占用块的情况下,我还在输出中添加了一些文本.
  • 有一些方法可以获得实际参数名称,这些名称更接近原始输出,但它们并不真正适合这里的响应.您可以查看merb-action-args,我们在其中编写了一些需要获取实际参数名称的代码.它适用于JRuby,Ruby 1.8.x,Ruby 1.9.1(带有gem)和Ruby 1.9 trunk(本机).
  • 这里的基本技术是存储当一个类的实例变量profilecomment被调用,当加入的方法中,然后施用.与前面的解决方案一样,method_added钩子用于跟踪何时添加新方法,但钩子不是每次都删除钩子,而是检查实例变量.应用AOP后将删除实例变量,因此它仅应用一次.如果多次使用同样的技术,可以进一步抽象.
  • 一般来说,我试图尽可能贴近你的"规范",这就是我包含Object.profile片段而不是内联实现它的原因.


mol*_*olf 7

好问题.这是我对实现的快速尝试(我没有尝试优化代码).我冒昧地把这个profile方法添加到 Module课堂上.通过这种方式,它将在每个类和模块定义中可用.将它提取到模块中并Module在需要时将其混合到类中会更好.

我也不知道是不是要让profile方法表现得像Ruby的public/ protected/ private关键字,但无论如何我都是这样实现的.调用后定义的所有方法profile都被分析,直到noprofile被调用.

class Module
  def profile
    require "benchmark"
    @profiled_methods ||= []
    class << self
      # Save any original method_added callback.
      alias_method :__unprofiling_method_added, :method_added
      # Create new callback.
      def method_added(method)
        # Possible infinite loop if we do not check if we already replaced this method.
        unless @profiled_methods.include?(method)
          @profiled_methods << method
          unbound_method = instance_method(method)
          define_method(method) do |*args|
            puts "#{self.class}##{method} was called with params #{args.join(", ")}"
            bench = Benchmark.measure do
              unbound_method.bind(self).call(*args)
            end
            puts "#{self.class}##{method} finished in %.5fs" % bench.real
          end
          # Call the original callback too.
          __unprofiling_method_added(method)
        end
      end
    end
  end

  def noprofile # What's the opposite of profile?
    class << self
      # Remove profiling callback and restore previous one.
      alias_method :method_added, :__unprofiling_method_added
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

您现在可以按如下方式使用它:

class Foo
  def self.method_added(method) # This still works.
    puts "Method '#{method}' has been added to '#{self}'."
  end

  profile

  def foo(arg1, arg2, arg3 = nil)
    puts "> body of foo"
    sleep 1
  end

  def bar(arg)
    puts "> body of bar"
  end

  noprofile

  def baz(arg)
    puts "> body of baz"
  end
end
Run Code Online (Sandbox Code Playgroud)

像往常一样调用方法:

foo = Foo.new
foo.foo(1, 2, 3)
foo.bar(2)
foo.baz(3)
Run Code Online (Sandbox Code Playgroud)

并获得基准测试输出(以及原始method_added回调的结果只是为了表明它仍然有效):

Method 'foo' has been added to 'Foo'.
Method 'bar' has been added to 'Foo'.
Method 'baz' has been added to 'Foo'.
Foo#foo was called with params 1, 2, 3
> body of foo
Foo#foo finished in 1.00018s
Foo#bar was called with params 2
> body of bar
Foo#bar finished in 0.00016s
> body of baz
Run Code Online (Sandbox Code Playgroud)

需要注意的一点是,使用Ruby元编程动态获取参数名称是不可能的.你必须解析原始的Ruby文件,这肯定是可能的,但有点复杂.有关 详细信息,请参阅parse_tree和ruby_parser gems.

一个有趣的改进是能够使用类中的Module类方法定义这种行为.能够做类似的事情会很酷:

class Module
  method_wrapper :profile do |*arguments|
    # Do something before calling method.
    yield *arguments  # Call original method.
    # Do something afterwards.
  end
end
Run Code Online (Sandbox Code Playgroud)

我将把这个元元编程练习再次留下.:-)