Rompe tus cadenas...
Ya lo decía Reincidentes en su mítica canción Carmén:
Y esto no (qué estás esperando)
cambiará (qué estás esperando)
sin luchar (rompe tus cadenas)
pa salir.
Y es que parece mentira como las cadenas y su manipulación vienen siendo un problema desde los tiempos de C. Digo esto por que últimamente me he encontrado con dos usos anómalos de cadenas en código que he visto:
El primer uso anómalo consiste en usar el + y el \n o el espacio para escribir cadenas. Solo afecta a la legibilidad y mantenibilidad del código, que no es poco, pero no al rendimiento, ya que la concatenación de cadenas se hará en tiempo de compilación. El código anómalo tendrá un aspecto similar a este:
const string sql =
"SELECT\n" +
"DepartmentID,\n" +
"Name,\n" +
"GroupName,\n" +
"ModifiedDate\n" +
"FROM\n" +
"HumanResources.Department\n" +
"WHERE\n" +
"ModifiedDate BETWEEN @startDate AND GetDate()\n";
El principal problema es que los \n o espacios, los + y la gran cantidad de comillas afectan negativamente a la legibilidad de nuestro código. Además la mantenibilidad se ve complicada por el hecho de que si olvidamos uno de los \n o espacios finales, en según que puntos, nuestra cadena puede dejar de ser valida. Por ejemplo, si olvidamos el \n después del FROM la sentencia SQL no será valida.
Otra variante de este uso anómalo es el uso inadecuado de StringBuilder y AppendLine. Si bien en este caso, los problemas de mantenibilidad son menos, pues no se corre el riesgo de olvidar uno de los espacios finales o de los \n, seguimos teniendo problemas de legibilidad y además introducimos problemas de rendimiento, pues la construcción de la cadena completa se hará en tiempo de ejecución y no en tiempo de compilación. En este caso el código anómalo tendrá un aspecto similar a este:
StringBuilder sql = new StringBuilder();
sql.AppendLine("SELECT");
sql.AppendLine(" DepartmentID,");
sql.AppendLine(" Name,");
sql.AppendLine(" GroupName,");
sql.AppendLine(" ModifiedDate");
sql.AppendLine("FROM");
sql.AppendLine(" HumanResources.Department");
sql.AppendLine("WHERE");
sql.AppendLine(" ModifiedDate BETWEEN @startDate AND GetDate()");
sql.ToString();
La solución a ambos problemas es la misma, utilizar cadenas verbatim. Utilizando esta característica podemos expresar la cadena de este modo:
const string sql =
@"SELECT
DepartmentID,
Name,
GroupName,
ModifiedDate
FROM
HumanResources.Department
WHERE
ModifiedDate BETWEEN @startDate AND GetDate()";
Ahora la pregunta es ¿cúal será la diferencia de rendimiento entre la aproximación que usa el StringBuilder y la aproximación que usa cadenas verbatim?. Para resolver la duda he hecho un pequeño programita (que se encuentra como adjunto a este post), los resultados son clarificadores, en diez millones de iteraciones, la diferencia de tiempos, medida usando System.Diagnostics.StopWatch, es notable: 28 segundos, frente a 12 milisegundos. Podríamos decir que usar cadenas verbatim es unas 2300 veces más eficiente, esto desde luego puede marcar una diferencia notable en algunos excenarios.
La moraleja de la historia es siempre que se usen cadenas estáticas permitir que sea el compilador quien las resuelva, en lugar de usar StringBuilder para componerlas y además usar las cadenas verbatim para mejorar la legibilidad del código.
Recomiendo que le deís un vistazo también a un artículo de mi compañero Jorge Serrano .NET -- Rendimiento iterando cadenas.