Is my current solution, (A, B, C, D, ...).GetHashCode(), guaranteed to always be the same for tuples with "Equal" items?
public class Pair
{
    public int X { get; set; }
    public int Y { get; set; }
    public Pair(int x, int y)
    {
        X = x;
        Y = y;
    }
    public override bool Equals(object other) => Equals(other as Pair);
    public virtual bool Equals(Pair other)
    {
        if (other is null)
        {
            return false;
        }
        if (object.ReferenceEquals(this, other))
        {
            return true;
        }
        if (this.GetType() != other.GetType())
        {
            return false;
        }
        return X == other.X && Y == other.Y;
    }
    public override int GetHashCode() => (X, Y).GetHashCode();
    public static bool operator ==(Pair lhs, Pair rhs)
    {
        if (lhs is null)
        {
            if (rhs is null)
            {
                return true;
            }
            return false;
        }
        return lhs.Equals(rhs);
    }
    public static bool operator !=(Pair lhs, Pair rhs) => !(lhs == rhs);
}
In this code always guaranteed to print 1:
var uniquePairs = new HashSet<Pair>();
uniquePairs.Add(new Pair(2, 4));
uniquePairs.Add(new Pair(2, 4));
uniquePairs.Add(new Pair(2, 4));
uniquePairs.Add(new Pair(2, 4));
Console.WriteLine(uniquePairs.Count);
What about for a greater number of non-trivial type properties?
What are reliable GetHashCode solutions that can be used for classes like these, which guarantee equal hashodes if all (not-necessarily-int) members are the same?
 
    