I should probably clarify that because of what I said about the 'Signature' attribute approach.
I iterate through each property in the class that isn't null or still at it's default value and combine them for the object's overall hash code. That makes sense in a value object, if the properties aren't equal they can't be the same thing, Address1(Flat 1, That Road, That City) is not the same as Address2(Flat 2, That Road, That City) but it is the same as Address3(Flat 1, That Road, That City).
If the addresses were an entity they'd have an Id. If they're not transient (have been persisted/have an id) then just compare the Id value - if they're equal then they're the same thing. If they aren't transient then effectively they're a value object, an entity without an Id. In that case you want to compare the property values to see if they're the same.
TransientAddress1(Flat 1, That Road, That City)
TransientAddress2(Flat 1, That Road, That City)
I suppose you could argue that they're not the same, because they're two different entity objects regardless of what properties they hold, but without being persisted I think they are equal, they contain the same values. You could persist one, in which case they'd no longer be equal because one would have an Id and one wouldn't. You could then persist the other, they'd now contain the same values but are no longer equal because you've persisted them as seperate entities, with different Ids. It's then down to your validation/constraint checks to determine whether a persisted address (as an entity) exists with the same property values and how you'd like to proceed.
If only comparing Ids I'd also first check whether the objects are of the same type before doing anything else. You could end up comparing the Ids of two entities that both have an Id of 100, but one could be a Recipe and one could be a House, in this case they're clearly not the same thing despite having equal Ids.