You have to Implement IEquatable<T> as well. Collections go to the IEquatable interface for comparison, which is type safe and will not cause boxing/unboxing when comparing value types.
As mentioned before you should override GetHashCode too.
If you are using a product like resharper the tool can auto-generate this for you. A common pattern is something like:
public virtual bool Equals(Entity other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return other.Id.Equals(Id);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (!(obj is Entity)) return false;
return Equals((Entity)obj);
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
Where you use the interface method to do all type based stuff and the overridden Equals(object) to check for type invariant conditions only, then cast and redirect to the interface method.
For general best practices for generating the Hash Code see John Skeets answer here.