如何检查变量是否真的响应:dup?

时间:2014-01-06 16:41:56

标签: ruby

我想使用value.respond_to?(:dup) ? value.dup : value来检查我是否可以复制一个对象,但它在布尔值,零或类似“基元”等情况下以TypeError失败。

我最终得到了:

begin
  value = value.dup
rescue
  #ignore, use the original if no dup-able (e.g nil, true, etc)
end

有更好的方法吗?

奖金:为什么它会回复:dup

不深dup,仅针对此问题。

编辑:想法:

  • obj.class.methods.include? :new很好,但有点过于苛刻,我觉得性能不好
  • Marshal看起来也有点矫枉过正
  • 一线救援可能是最好的解决方案,但目前无法进行类型特定的一线救援(IIUC matz is on that!),而且@JörgWMittag提到错误。
  • 我个人认为在对象级别定义dup是错误的。

所以,引用@Linuxios

  

没有更好的方法

5 个答案:

答案 0 :(得分:6)

你可以这样写一行:

value = value.dup rescue value

很清楚。

定义dup方法是标准的,可以为无法复制的类型引发TypeError。因此任何对象都会“回应”它。你必须打电话给它并检查一下begin-rescue-end。

答案 1 :(得分:5)

没有更好的方法。 dup在Object上定义,这意味着任何想要不响应它的类都需要重载它以引发异常。 NilClassTrueClassFalseClassNumber都是Object的子类。这意味着他们必须覆盖该方法以抛出错误。

解决这个问题的一种方法,如果你正在寻找一个深层拷贝,就是使用通常的Marshal.load(Marshal.dump(obj))来处理数字,bool和nil就好了。

例如:

1.9.3-p392 :001 > obj = "hi"
 => "hi"
1.9.3-p392 :002 > Marshal.load(Marshal.dump(obj)).object_id != obj.object_id
 => true
1.9.3-p392 :003 > obj = 3
 => 3
1.9.3-p392 :004 > Marshal.load(Marshal.dump(obj)).object_id != obj.object_id
 => false

答案 2 :(得分:2)

def dupable?(obj)
  obj.class.methods.include? :new
end

dupable?(1)     # => false
dupable?(3.2)   # => false
dupable?(:a)    # => false
dupable?(true)  # => false
dupable?(nil)   # => false
dupable?("cat") # => true

答案 3 :(得分:1)

我认为它响应dup的原因是Class继承自具有dup方法的Object。

似乎在对象的dup方法中检查'特殊常量'并引发您看到的错误:

VALUE
rb_obj_dup(VALUE obj)
{
    VALUE dup;

    if (rb_special_const_p(obj)) {
        rb_raise(rb_eTypeError, "can't dup %s", rb_obj_classname(obj));
    }
    dup = rb_obj_alloc(rb_obj_class(obj));
    init_copy(dup, obj);
    rb_funcall(dup, id_init_dup, 1, obj);

    return dup;
}

我想您唯一能做的就是在方法中检查这些特殊常量。

答案 4 :(得分:1)

有一种更好的方法,但不确定是否检查错误消息:

begin
    value = value.dup
rescue TypeError => e
    # !!! not sure about the following line
    raise unless e.message == "can't dup %s" % value.class.name
    #ignore, use the original if no dup-able (e.g nil, true, etc)
end