Гарантированный способ сравнения объектов по идентичности в Ruby
Что такое гарантированный способ сравнить два объекта по их идентичности в Ruby? Учитывая две переменные, я хочу вернуть true, если переменные указывают на один и тот же объект в памяти.
Для большинства объектов Ruby метод equal?
сравнивается по идентификатору:
f = g = Object.new
p f.equal? g # => true
Однако это не работает для всех объектов. Например:
class F
def ==(obj) false end
def ===(obj) false end
def eql?(obj) false end
def equal?(obj) false end
def object_id; self end
end
f = g = F.new
p f == g # => false
p f === g # => false
p f.eql? g # => false
p f.equal? g # => false
p f.object_id == g.object_id # => false
Что такое надежный/гарантированный способ сравнения двух объектов по идентичности, которые нельзя победить?
Это чисто интеллектуальный вопрос. Ответ на любой вопрос, который начинается с "почему", вероятно, будет "Потому что мне любопытно".
Ответы
Ответ 1
Вы можете захватить несвязанную версию Object#object_id
, привязать ее к рассматриваемому объекту и посмотреть, что он говорит. Учитывая ваш класс F
с одним добавлением:
class F
# ...
def inspect; 'pancakes!' end # Just so we can tell what we have later.
end
Тогда:
>> f = F.new
>> f.object_id
=> pancakes!
>> unbound_object_id = Object.instance_method(:object_id)
>> unbound_object_id.bind(f).call
=> 2153000340
>> ObjectSpace._id2ref(2153000340).inspect
=> "pancakes!"
Конечно, если кто-то открывает Object и заменяет object_id
, тогда вам не повезло, но это будет наименьшим из ваших проблем, если кто-то это сделает. Если вы можете захватить unbound_object_id
UnboundMethod, прежде чем что-либо еще будет загружено, то не имеет значения, изменит ли кто-либо Object#object_id
как ваш unbound_object_id
по-прежнему будет оригинальным правильным.
Таким образом, этот раунд взлома дает вам надежный object_id
для любого объекта (с учетом вышеописанных предостережений). Теперь вы можете захватить и сравнить идентификаторы объектов, чтобы получить надежное сравнение.
Ответ 2
Используйте BasicObject#equal?
. Согласно Ruby documentation:
В отличие от ==, равный? метод никогда не должен быть переопределен подклассами: он используется для определения идентичности объекта (то есть a.equal? (b) iff a - это тот же объект, что и b).
Нужна более сильная гарантия? AFAIK не может получить его, BaseObject#object_id
, ObjectSpace._id2ref
, Hash#compare_by_identity
может быть переопределен или обезьяна исправлена (по крайней мере, это мое личное убеждение).
Ответ 3
Ответ mu слишком короткий, это замечательно, но есть другой способ, которым вы можете это сделать, используя специальную функцию класса Hash:
def compare_by_identity(x, y)
h = {}.compare_by_identity
h[x] = 1
h[y] = 2
h.keys.size == 1
end
Функция compare_by_identity
была добавлена в Ruby 1.9.2, поэтому эта функция не будет работать в более ранних версиях. Я думаю, что му слишком короткий ответ лучше.