I've read some questions here, related to GetHashCode correct implementation. What I didn't found is when should I implement this method.
In my specific case, I've build a simple immutable struct :
public struct MyStruct
{
    private readonly Guid m_X;
    private readonly string m_Y;
    private readonly string m_Z;
    public Guid string X
    {
        get { return m_X; }
    }
    public string Y
    {
        get { return m_Y; }
    }
    public string Z
    {
        get { return m_Z; }
    }
    public MyStruct(Guid x, string y, string z)
    {
        if (x == Guid.Empty) throw new ArgumentException("x cannot be equals to Guid.Empty", "x");
        if (string.IsNullOrEmpty(y)) throw new ArgumentException("y cannot be null or empty", "y");
        if (string.IsNullOrEmpty(Z)) throw new ArgumentException("Z cannot be null or empty", "Z");
        this.m_X = x;
        this.m_Y = y;
        this.m_Z = Z;
    }
    public override int GetHashCode()
    {
        var x = 17;
        x = x * 23 + m_X.GetHashCode();
        x = x * 23 + m_Y.GetHashCode();
        x = x * 23 + m_Z.GetHashCode();
        return x;
    }
}
In this case, I've implemented GetHashCode, but was it mandatory? Isn't the base object.GetHashCode implementation itself handling this scenario?
[Edit] a bit of background: I have some string to parse and produces. This strings are part of a 3rd party custom query language. The string are always of the form X|Y|Z. I want to avoid developers to play with string.Split and string concatenation by providing this custom struct. Lastly, the struct will contains this two supplementary methods :
    public override string ToString()
    {
        return m_X.ToString() + "|" + m_Y + "|" + m_Z;
    }
    public static MyString Parse(string stringToParse)
    {
         // implementation omitted
    }
 
     
    