What needs to be overridden in a struct to ensure equality operates properly?

You should also implement IEquatable<T>. Here is an excerpt from Framework Design Guidelines:

DO implement IEquatable on value types. The Object.Equals method on value types causes boxing, and its default implementation is not very effcient because it uses refection. IEquatable.Equals can offer much better performance and can be implemented so that it does not cause boxing.

public struct Int32 : IEquatable<Int32> {
    public bool Equals(Int32 other){ ... }
}

DO follow the same guidelines as for overriding Object.Equals when implementing IEquatable.Equals. See section 8.7.1 for detailed guidelines on overriding Object.Equals


An example from msdn

public struct Complex 
{
   double re, im;
   public override bool Equals(Object obj) 
   {
        return obj is Complex c && this == c;
   }
   public override int GetHashCode() 
   {
      return re.GetHashCode() ^ im.GetHashCode();
   }
   public static bool operator ==(Complex x, Complex y) 
   {
      return x.re == y.re && x.im == y.im;
   }
   public static bool operator !=(Complex x, Complex y) 
   {
      return !(x == y);
   }
}