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

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

我最终得到了:

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

有没有更好的办法?

奖金:它为什么回应:dup

不是深层次的dup ,只是针对这个问题。

编辑 :思考:

  • obj.class.methods.include? :new obj.class.methods.include? :new的很好,但有点过于hackish我认为它有不好的表现
  • Marshal看起来也有点矫枉过正
  • 一线救援可能是最好的解决方案,但目前不可能进行类型特定的一线救援( IIUC matz就是这样! ),并且@JörgWMittag提到它的错误。
  • 我个人认为在对象级别定义的dup是错误的。

所以,引用@Linuxios

没有更好的方法

没有更好的方法。 在Object上定义了dup ,这意味着任何想要不响应它的类都需要重载它以引发exception。 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 

您可以通过这种方式将其写为一行:

 value = value.dup rescue value 

非常清楚。

定义一个dup方法是标准的,该方法为无法复制的类型引发TypeError 。 因此任何对象都会“回应”它。 你真的必须打电话给它并检查一下开始救援结束。

 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 

我认为它响应dup的原因是Classinheritance自Object,它有一个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; } 

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

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

 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 (eg nil, true, etc) end