class UnboundMethod
Ruby 支持两种形式的对象化方法。类 Method 用于表示与特定对象关联的方法:这些方法对象绑定到该对象。可以通过 Object#method 创建对象的绑定方法对象。
Ruby 还支持未绑定方法;即不与特定对象关联的方法对象。这些可以通过调用 Module#instance_method 或通过对绑定方法对象调用 unbind 来创建。这两种操作的结果都是 UnboundMethod 对象。
未绑定方法只能在绑定到对象后调用。该对象必须是方法原始类的 kind_of?。
class Square def area @side * @side end def initialize(side) @side = side end end area_un = Square.instance_method(:area) s = Square.new(12) area = area_un.bind(s) area.call #=> 144
未绑定方法是对方法在被对象化时的一个引用:后续对底层类的更改不会影响未绑定方法。
class Test def test :original end end um = Test.instance_method(:test) class Test def test :modified end end t = Test.new t.test #=> :modified um.bind(t).call #=> :original
Public Instance Methods
Source
#define unbound_method_eq method_eq
两个未绑定方法对象相等,如果它们引用相同的方法定义。
Array.instance_method(:each_slice) == Enumerable.instance_method(:each_slice) #=> true Array.instance_method(:sum) == Enumerable.instance_method(:sum) #=> false, Array redefines the method for efficiency
Source
static VALUE
method_arity_m(VALUE method)
{
int n = method_arity(method);
return INT2FIX(n);
}
返回一个指示方法接受的参数数量。对于接受固定数量参数的方法,返回一个非负整数。对于接受可变数量参数的 Ruby 方法,返回 -n-1,其中 n 是必需参数的数量。关键字参数将被视为一个附加参数,如果任何关键字参数是必需的,则该参数是强制性的。对于用 C 编写的方法,如果调用接受可变数量的参数,则返回 -1。
class C def one; end def two(a); end def three(*a); end def four(a, b); end def five(a, b, *c); end def six(a, b, *c, &d); end def seven(a, b, x:0); end def eight(x:, y:); end def nine(x:, y:, **z); end def ten(*a, x:, y:); end end c = C.new c.method(:one).arity #=> 0 c.method(:two).arity #=> 1 c.method(:three).arity #=> -1 c.method(:four).arity #=> 2 c.method(:five).arity #=> -3 c.method(:six).arity #=> -3 c.method(:seven).arity #=> -3 c.method(:eight).arity #=> 1 c.method(:nine).arity #=> 1 c.method(:ten).arity #=> -2 "cat".method(:size).arity #=> 0 "cat".method(:replace).arity #=> 1 "cat".method(:squeeze).arity #=> -1 "cat".method(:count).arity #=> -1
Source
static VALUE
umethod_bind(VALUE method, VALUE recv)
{
VALUE methclass, klass, iclass;
const rb_method_entry_t *me;
const struct METHOD *data;
TypedData_Get_Struct(method, struct METHOD, &method_data_type, data);
convert_umethod_to_method_components(data, recv, &methclass, &klass, &iclass, &me, true);
struct METHOD *bound;
method = TypedData_Make_Struct(rb_cMethod, struct METHOD, &method_data_type, bound);
RB_OBJ_WRITE(method, &bound->recv, recv);
RB_OBJ_WRITE(method, &bound->klass, klass);
RB_OBJ_WRITE(method, &bound->iclass, iclass);
RB_OBJ_WRITE(method, &bound->owner, methclass);
RB_OBJ_WRITE(method, &bound->me, me);
return method;
}
将 umeth 绑定到 obj。如果 Klass 是从中获取 umeth 的类,则 obj.kind_of?(Klass) 必须为 true。
class A def test puts "In test, class = #{self.class}" end end class B < A end class C < B end um = B.instance_method(:test) bm = um.bind(C.new) bm.call bm = um.bind(B.new) bm.call bm = um.bind(A.new) bm.call
产生
In test, class = C In test, class = B prog.rb:16:in `bind': bind argument must be an instance of B (TypeError) from prog.rb:16
Source
static VALUE
umethod_bind_call(int argc, VALUE *argv, VALUE method)
{
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
VALUE recv = argv[0];
argc--;
argv++;
VALUE passed_procval = rb_block_given_p() ? rb_block_proc() : Qnil;
rb_execution_context_t *ec = GET_EC();
const struct METHOD *data;
TypedData_Get_Struct(method, struct METHOD, &method_data_type, data);
const rb_callable_method_entry_t *cme = rb_callable_method_entry(CLASS_OF(recv), data->me->called_id);
if (data->me == (const rb_method_entry_t *)cme) {
vm_passed_block_handler_set(ec, proc_to_block_handler(passed_procval));
return rb_vm_call_kw(ec, recv, cme->called_id, argc, argv, cme, RB_PASS_CALLED_KEYWORDS);
}
else {
VALUE methclass, klass, iclass;
const rb_method_entry_t *me;
convert_umethod_to_method_components(data, recv, &methclass, &klass, &iclass, &me, false);
struct METHOD bound = { recv, klass, 0, methclass, me };
return call_method_data(ec, &bound, argc, argv, passed_procval, RB_PASS_CALLED_KEYWORDS);
}
}
将 umeth 绑定到 recv,然后使用指定的参数调用该方法。这在语义上等同于 umeth.bind(recv).call(args, ...)。
Source
static VALUE
method_clone(VALUE self)
{
VALUE clone;
struct METHOD *orig, *data;
TypedData_Get_Struct(self, struct METHOD, &method_data_type, orig);
clone = TypedData_Make_Struct(CLASS_OF(self), struct METHOD, &method_data_type, data);
rb_obj_clone_setup(self, clone, Qnil);
RB_OBJ_WRITE(clone, &data->recv, orig->recv);
RB_OBJ_WRITE(clone, &data->klass, orig->klass);
RB_OBJ_WRITE(clone, &data->iclass, orig->iclass);
RB_OBJ_WRITE(clone, &data->owner, orig->owner);
RB_OBJ_WRITE(clone, &data->me, rb_method_entry_clone(orig->me));
return clone;
}
返回此方法的克隆。
class A def foo return "bar" end end m = A.new.method(:foo) m.call # => "bar" n = m.clone.call # => "bar"
两个未绑定方法对象相等,如果它们引用相同的方法定义。
Array.instance_method(:each_slice) == Enumerable.instance_method(:each_slice) #=> true Array.instance_method(:sum) == Enumerable.instance_method(:sum) #=> false, Array redefines the method for efficiency
Source
static VALUE
method_hash(VALUE method)
{
struct METHOD *m;
st_index_t hash;
TypedData_Get_Struct(method, struct METHOD, &method_data_type, m);
hash = rb_hash_start((st_index_t)m->recv);
hash = rb_hash_method_entry(hash, m->me);
hash = rb_hash_end(hash);
return ST2FIX(hash);
}
返回与方法对象对应的哈希值。
另请参阅 Object#hash。
Source
static VALUE
method_inspect(VALUE method)
{
struct METHOD *data;
VALUE str;
const char *sharp = "#";
VALUE mklass;
VALUE defined_class;
TypedData_Get_Struct(method, struct METHOD, &method_data_type, data);
str = rb_sprintf("#<% "PRIsVALUE": ", rb_obj_class(method));
mklass = data->iclass;
if (!mklass) mklass = data->klass;
if (RB_TYPE_P(mklass, T_ICLASS)) {
/* TODO: I'm not sure why mklass is T_ICLASS.
* UnboundMethod#bind() can set it as T_ICLASS at convert_umethod_to_method_components()
* but not sure it is needed.
*/
mklass = RBASIC_CLASS(mklass);
}
if (data->me->def->type == VM_METHOD_TYPE_ALIAS) {
defined_class = data->me->def->body.alias.original_me->owner;
}
else {
defined_class = method_entry_defined_class(data->me);
}
if (RB_TYPE_P(defined_class, T_ICLASS)) {
defined_class = RBASIC_CLASS(defined_class);
}
if (UNDEF_P(data->recv)) {
// UnboundMethod
rb_str_buf_append(str, rb_inspect(defined_class));
}
else if (RCLASS_SINGLETON_P(mklass)) {
VALUE v = RCLASS_ATTACHED_OBJECT(mklass);
if (UNDEF_P(data->recv)) {
rb_str_buf_append(str, rb_inspect(mklass));
}
else if (data->recv == v) {
rb_str_buf_append(str, rb_inspect(v));
sharp = ".";
}
else {
rb_str_buf_append(str, rb_inspect(data->recv));
rb_str_buf_cat2(str, "(");
rb_str_buf_append(str, rb_inspect(v));
rb_str_buf_cat2(str, ")");
sharp = ".";
}
}
else {
mklass = data->klass;
if (RCLASS_SINGLETON_P(mklass)) {
VALUE v = RCLASS_ATTACHED_OBJECT(mklass);
if (!(RB_TYPE_P(v, T_CLASS) || RB_TYPE_P(v, T_MODULE))) {
do {
mklass = RCLASS_SUPER(mklass);
} while (RB_TYPE_P(mklass, T_ICLASS));
}
}
rb_str_buf_append(str, rb_inspect(mklass));
if (defined_class != mklass) {
rb_str_catf(str, "(% "PRIsVALUE")", defined_class);
}
}
rb_str_buf_cat2(str, sharp);
rb_str_append(str, rb_id2str(data->me->called_id));
if (data->me->called_id != data->me->def->original_id) {
rb_str_catf(str, "(%"PRIsVALUE")",
rb_id2str(data->me->def->original_id));
}
if (data->me->def->type == VM_METHOD_TYPE_NOTIMPLEMENTED) {
rb_str_buf_cat2(str, " (not-implemented)");
}
// parameter information
{
VALUE params = rb_method_parameters(method);
VALUE pair, name, kind;
const VALUE req = ID2SYM(rb_intern("req"));
const VALUE opt = ID2SYM(rb_intern("opt"));
const VALUE keyreq = ID2SYM(rb_intern("keyreq"));
const VALUE key = ID2SYM(rb_intern("key"));
const VALUE rest = ID2SYM(rb_intern("rest"));
const VALUE keyrest = ID2SYM(rb_intern("keyrest"));
const VALUE block = ID2SYM(rb_intern("block"));
const VALUE nokey = ID2SYM(rb_intern("nokey"));
int forwarding = 0;
rb_str_buf_cat2(str, "(");
if (RARRAY_LEN(params) == 3 &&
RARRAY_AREF(RARRAY_AREF(params, 0), 0) == rest &&
RARRAY_AREF(RARRAY_AREF(params, 0), 1) == ID2SYM('*') &&
RARRAY_AREF(RARRAY_AREF(params, 1), 0) == keyrest &&
RARRAY_AREF(RARRAY_AREF(params, 1), 1) == ID2SYM(idPow) &&
RARRAY_AREF(RARRAY_AREF(params, 2), 0) == block &&
RARRAY_AREF(RARRAY_AREF(params, 2), 1) == ID2SYM('&')) {
forwarding = 1;
}
for (int i = 0; i < RARRAY_LEN(params); i++) {
pair = RARRAY_AREF(params, i);
kind = RARRAY_AREF(pair, 0);
if (RARRAY_LEN(pair) > 1) {
name = RARRAY_AREF(pair, 1);
}
else {
// FIXME: can it be reduced to switch/case?
if (kind == req || kind == opt) {
name = rb_str_new2("_");
}
else if (kind == rest || kind == keyrest) {
name = rb_str_new2("");
}
else if (kind == block) {
name = rb_str_new2("block");
}
else if (kind == nokey) {
name = rb_str_new2("nil");
}
else {
name = Qnil;
}
}
if (kind == req) {
rb_str_catf(str, "%"PRIsVALUE, name);
}
else if (kind == opt) {
rb_str_catf(str, "%"PRIsVALUE"=...", name);
}
else if (kind == keyreq) {
rb_str_catf(str, "%"PRIsVALUE":", name);
}
else if (kind == key) {
rb_str_catf(str, "%"PRIsVALUE": ...", name);
}
else if (kind == rest) {
if (name == ID2SYM('*')) {
rb_str_cat_cstr(str, forwarding ? "..." : "*");
}
else {
rb_str_catf(str, "*%"PRIsVALUE, name);
}
}
else if (kind == keyrest) {
if (name != ID2SYM(idPow)) {
rb_str_catf(str, "**%"PRIsVALUE, name);
}
else if (i > 0) {
rb_str_set_len(str, RSTRING_LEN(str) - 2);
}
else {
rb_str_cat_cstr(str, "**");
}
}
else if (kind == block) {
if (name == ID2SYM('&')) {
if (forwarding) {
rb_str_set_len(str, RSTRING_LEN(str) - 2);
}
else {
rb_str_cat_cstr(str, "...");
}
}
else {
rb_str_catf(str, "&%"PRIsVALUE, name);
}
}
else if (kind == nokey) {
rb_str_buf_cat2(str, "**nil");
}
if (i < RARRAY_LEN(params) - 1) {
rb_str_buf_cat2(str, ", ");
}
}
rb_str_buf_cat2(str, ")");
}
{ // source location
VALUE loc = rb_method_location(method);
if (!NIL_P(loc)) {
rb_str_catf(str, " %"PRIsVALUE":%"PRIsVALUE,
RARRAY_AREF(loc, 0), RARRAY_AREF(loc, 1));
}
}
rb_str_buf_cat2(str, ">");
return str;
}
返回底层方法的易于阅读的描述。
"cat".method(:count).inspect #=> "#<Method: String#count(*)>" (1..3).method(:map).inspect #=> "#<Method: Range(Enumerable)#map()>"
在后一种情况下,方法描述包括原始方法的“所有者”(Enumerable 模块,该模块包含在 Range 中)。
inspect 还提供了方法参数名称(调用序列)和源位置(如果可能)。
require 'net/http' Net::HTTP.method(:get).inspect #=> "#<Method: Net::HTTP.get(uri_or_host, path=..., port=...) <skip>/lib/ruby/2.7.0/net/http.rb:457>"
参数定义中的 ... 表示参数是可选的(具有某些默认值)。
对于用 C 定义的方法(语言核心和扩展),无法提取位置和参数名称,并且只提供通用信息,形式为 *(任意数量的参数)或 _(某些位置参数)。
"cat".method(:count).inspect #=> "#<Method: String#count(*)>" "cat".method(:+).inspect #=> "#<Method: String#+(_)>""
Source
static VALUE
method_name(VALUE obj)
{
struct METHOD *data;
TypedData_Get_Struct(obj, struct METHOD, &method_data_type, data);
return ID2SYM(data->me->called_id);
}
返回方法的名称。
Source
static VALUE
method_original_name(VALUE obj)
{
struct METHOD *data;
TypedData_Get_Struct(obj, struct METHOD, &method_data_type, data);
return ID2SYM(data->me->def->original_id);
}
返回方法的原始名称。
class C def foo; end alias bar foo end C.instance_method(:bar).original_name # => :foo
Source
static VALUE
method_owner(VALUE obj)
{
struct METHOD *data;
TypedData_Get_Struct(obj, struct METHOD, &method_data_type, data);
return data->owner;
}
返回定义该方法的类或模块。换句话说,
meth.owner.instance_methods(false).include?(meth.name) # => true
保持有效,只要方法未被移除/未定义/替换(如果方法是私有的,则使用 private_instance_methods 而不是 instance_methods)。
另请参阅 Method#receiver。
(1..3).method(:map).owner #=> Enumerable
Source
static VALUE
rb_method_parameters(VALUE method)
{
return method_def_parameters(rb_method_def(method));
}
返回此方法的参数信息。
def foo(bar); end method(:foo).parameters #=> [[:req, :bar]] def foo(bar, baz, bat, &blk); end method(:foo).parameters #=> [[:req, :bar], [:req, :baz], [:req, :bat], [:block, :blk]] def foo(bar, *args); end method(:foo).parameters #=> [[:req, :bar], [:rest, :args]] def foo(bar, baz, *args, &blk); end method(:foo).parameters #=> [[:req, :bar], [:req, :baz], [:rest, :args], [:block, :blk]]
Source
VALUE
rb_method_location(VALUE method)
{
return method_def_location(rb_method_def(method));
}
返回定义方法的源位置。返回的 Array 包含
(1) the Ruby source filename (2) the line number where the definition starts (3) the column number where the definition starts (4) the line number where the definition ends (5) the column number where the definitions ends
如果方法不是在 Ruby 中定义的(即,原生方法),此方法将返回 nil。
Source
static VALUE
method_super_method(VALUE method)
{
const struct METHOD *data;
VALUE super_class, iclass;
ID mid;
const rb_method_entry_t *me;
TypedData_Get_Struct(method, struct METHOD, &method_data_type, data);
iclass = data->iclass;
if (!iclass) return Qnil;
if (data->me->def->type == VM_METHOD_TYPE_ALIAS && data->me->defined_class) {
super_class = RCLASS_SUPER(rb_find_defined_class_by_owner(data->me->defined_class,
data->me->def->body.alias.original_me->owner));
mid = data->me->def->body.alias.original_me->def->original_id;
}
else {
super_class = RCLASS_SUPER(RCLASS_ORIGIN(iclass));
mid = data->me->def->original_id;
}
if (!super_class) return Qnil;
me = (rb_method_entry_t *)rb_callable_method_entry_with_refinements(super_class, mid, &iclass);
if (!me) return Qnil;
return mnew_internal(me, me->owner, iclass, data->recv, mid, rb_obj_class(method), FALSE, FALSE);
}
返回一个超类的方法(当使用 super 时将调用该方法),如果超类上没有该方法,则返回 nil。
返回底层方法的易于阅读的描述。
"cat".method(:count).inspect #=> "#<Method: String#count(*)>" (1..3).method(:map).inspect #=> "#<Method: Range(Enumerable)#map()>"
在后一种情况下,方法描述包括原始方法的“所有者”(Enumerable 模块,该模块包含在 Range 中)。
inspect 还提供了方法参数名称(调用序列)和源位置(如果可能)。
require 'net/http' Net::HTTP.method(:get).inspect #=> "#<Method: Net::HTTP.get(uri_or_host, path=..., port=...) <skip>/lib/ruby/2.7.0/net/http.rb:457>"
参数定义中的 ... 表示参数是可选的(具有某些默认值)。
对于用 C 定义的方法(语言核心和扩展),无法提取位置和参数名称,并且只提供通用信息,形式为 *(任意数量的参数)或 _(某些位置参数)。
"cat".method(:count).inspect #=> "#<Method: String#count(*)>" "cat".method(:+).inspect #=> "#<Method: String#+(_)>""