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?