Revisando mis links diarios me topo con esta noticia del famoso Ayende (alias de Oren Eini) acerca del uso de Objects Initializers en using statements, explico un poco de que se trata:
Como ustedes recuerdan, objetos que obedecen a la Dispose Pattern, como por ejemplo los DataReaders en ADO.net, mantienen un patron de “destrucción” con instrucciones de liberación de recursos o instrucciones de destrucción del objeto, en otras palabras, que hacer cuando el objeto ya no es necesario (no implica que deterministicamente destruimos al objeto, eso es otrotema):
// Hacemos lo que queremos con el objeto repository var repositorio = new UserRepository(); repositorio.Dispose();
Bien, de igual manera el compilador de C# 3.0 nos ofrece la opción de inicializadores de objeto, imaginemos que el objeto repositorio pueda tener un parámetro opcional con la información del “nombre” del repositorio (nuevamente un ejemplo silly):
// forma C# 2.0
var repositorio = new UserRepository();
repositorio.Name = "Mi repo";
// forma C# 3.0 con object initializers
var repositorio = new UserRepository() { Name = "Mi repo" };
Internamente el compilador agrega la siguiente línea con un setter a la propiedad, interesante no?.
Bien, como recordaran, la Disposable Pattern es tan común que hay una forma de “resumirla” o un shortcut en la framework:
using (var repositorio = new UserRepository()) {
// Hago lo que quiera con el objeto
}
Si el objeto generara una excepción DENTRO del bloque de igual manera se correría Dispose, eso hace más segura nuestra implementación.
Bien, que tal si mezclamos ambas?:
using (var repo = new UserRepository() { Name = SimpleClass.GetName() }) {
// Blah
}
// PROBLEMA!!! lo "equivalente" generado
var repo = new UserRepository();
repo.Name = SimpleClass.GetName();
using (repo) {
// Blah
}
Aunque mi ejemplo es realmente trivial y hasta “tonto” podemos observar claramente el problema, si el inicializador “repo.Name = ’blah’” fallara, NUNCA entraríamos al using, por lo tanto no se correría Dispose(), o sea, que recursos no se liberarían como nosotros esperaramos.
Tengo entendido que esto es “by design” (realmente no se porqué), y que el compilador de Mono (el MCS) lo genera de la manera “ideal”. Esperemos que para C# 4.0 compiler esto sea resuelto, mientras tanto, eviten object initializers en declaraciones de sentencias using.
Hasta la próxima!
UPDATE:
Para contestar uno de los comentarios: si, si definimos tu "conexión" de la manera:
using (var con = new SqlConnection() { MyProperty = int.Parse("error") }) {
// Buh! nunca entro aqui! (ni corro dispose!)
}
En ese caso se genera una instancia con nombre "impronunciable" y se usa el setter, para luego entrar al bloque using. Eso involucra que cuando falle el setter de la propiedad tu objeto nunca entrará al bloque using, para evitar eso deberiamos cambiar el código para que luzca algo asi:
using(var con = new SqlConnection()) {
con.MyProperty = int.Parse("error");
}
La última sentencia funcionará como esperamos, se creará una excepción, pero debido a que la excepción se generó dentro del bloque se correrá exitosamente el Dispose del objeto SqlConnection.
