¿Se garantiza que los contratos de código se evalúen antes de llamar a los constructores encadenados?
Antes de comenzar a usar los Contratos de Código, a veces me encontraba con problemas relacionados con la validación de parámetros cuando utilizaba el encadenamiento de constructores.
Esto es más fácil de explicar con un ejemplo (creado):
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");
}
}
Quiero elTest(string)
constructor para encadenar elTest(int)
constructor, y para ello utilizoint.Parse()
.
Por supuesto,int.Parse()
no le gusta tener un argumento nulo, así que sis es nulo que tirará antes de que alcance las líneas de validación:
if (s == null)
throw new ArgumentNullException("s");
Lo que hace inútil ese cheque.
¿Cómo arreglar eso? Bueno, a veces solía hacer esto:
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);
}
}
Eso es un poco incómodo, y el seguimiento de la pila no es ideal cuando falla, pero funciona.
Ahora, a lo largo de los contratos de código, comencé a usarlos:
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);
}
}
Todo bien y bien. Funciona bien. Pero luego descubro que puedo hacer esto:
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);
}
}
Y luego si lo hagovar test = new Test(null)
, laContract.Requires(s != null)
es ejecutadoantes de this(int.Parse(s))
. Esto significa que puedo acabar con elconvertArg()
prueba en total!
Así que, a mis preguntas reales:
¿Está este comportamiento documentado en alguna parte?¿Puedo confiar en este comportamiento al escribir contratos de código para constructores encadenados como este?¿Hay alguna otra forma en que debería estar acercándome a esto?