Czy kontrakty z kodem są gwarantowane do oceny przed wywołaniem połączonych konstruktorów?

Zanim zacząłem korzystać z kontraktów kodu, czasami wpadałem na skrzypliwość związaną z walidacją parametrów podczas korzystania z łączenia łańcuchowego konstruktora.

Najłatwiej to wyjaśnić za pomocą (wymyślonego) przykładu:

class Test
{
    public Test(int i)
    {
        if (i == 0)
            throw new ArgumentOutOfRangeException("i", i, "i can't be 0");
    }

    public Test(string s): this(int.Parse(s))
    {
        if (s == null)
            throw new ArgumentNullException("s");
    }
}

ChcęTest(string) konstruktor do łączenia łańcuchówTest(int) konstruktor i do tego używamint.Parse().

Oczywiście,int.Parse() nie lubi mieć pustego argumentu, więc jeślis ma wartość NULL, zanim wróci do linii walidacyjnych:

if (s == null)
    throw new ArgumentNullException("s");

co czyni to sprawdzenie bezużytecznym.

Jak to naprawić? Czasami to robiłem:

class Test
{
    public Test(int i)
    {
        if (i == 0)
            throw new ArgumentOutOfRangeException("i", i, "i can't be 0");
    }

    public Test(string s): this(convertArg(s))
    {
    }

    static int convertArg(string s)
    {
        if (s == null)
            throw new ArgumentNullException("s");

        return int.Parse(s);
    }
}

To trochę dziwne, a ślad stosu nie jest idealny, gdy zawiedzie, ale działa.

Teraz, wraz z kontraktami Code, zaczynam ich używać:

class Test
{
    public Test(int i)
    {
        Contract.Requires(i != 0);
    }

    public Test(string s): this(convertArg(s))
    {
    }

    static int convertArg(string s)
    {
        Contract.Requires(s != null);
        return int.Parse(s);
    }
}

Wszystko dobrze i dobrze. To działa dobrze. Ale potem odkrywam, że mogę to zrobić:

class Test
{
    public Test(int i)
    {
        Contract.Requires(i != 0);
    }

    public Test(string s): this(int.Parse(s))
    {
        // This line is executed before this(int.Parse(s))
        Contract.Requires(s != null);
    }
}

A potem, jeśli to zrobięvar test = new Test(null), theContract.Requires(s != null) jest wykonywanyprzed this(int.Parse(s)). Oznacza to, że mogę zrezygnować zconvertArg() przetestuj całkowicie!

A więc do moich faktycznych pytań:

Czy to zachowanie jest udokumentowane w dowolnym miejscu?Czy mogę polegać na tym zachowaniu, pisząc kontrakty kodu dla takich łańcuchów konstruktorów?Czy powinienem podejść do tego w inny sposób?

questionAnswers(1)

yourAnswerToTheQuestion