Tengo un programa escrito con sintaxis fluida. La cadena de métodos tiene un "final" definitivo, antes del cual nada útil realmente se hace en el código (piense que la generación de consultas de NBuilder o Linq-to-SQL no llega a la base de datos hasta iterar sobre nuestros objetos con, digamos, ToList())¿Cómo hacer valer el uso del valor de retorno de un método en C#?
El problema que tengo es que hay confusión entre otros desarrolladores sobre el uso correcto del código. Están descuidando el método de "finalización" (¡y nunca "haciendo nada")!
Estoy interesado en aplicando el uso del valor de retorno de algunos de mis métodos para que nunca podamos "terminar la cadena" sin llamar a ese método "Finalizar()" o "Guardar()" que realmente la obra.
Considere el siguiente código:
//The "factory" class the user will be dealing with
public class FluentClass
{
//The entry point for this software
public IntermediateClass<T> Init<T>()
{
return new IntermediateClass<T>();
}
}
//The class that actually does the work
public class IntermediateClass<T>
{
private List<T> _values;
//The user cannot call this constructor
internal IntermediateClass<T>()
{
_values = new List<T>();
}
//Once generated, they can call "setup" methods such as this
public IntermediateClass<T> With(T value)
{
var instance = new IntermediateClass<T>() { _values = _values };
instance._values.Add(value);
return instance;
}
//Picture "lazy loading" - you have to call this method to
//actually do anything worthwhile
public void Save()
{
var itemCount = _values.Count();
. . . //save to database, write a log, do some real work
}
}
Como se puede ver, el uso adecuado de este código sería algo así como:
new FluentClass().Init<int>().With(-1).With(300).With(42).Save();
El problema es que las personas están utilizando de esta manera (pensamiento logra lo mismo que el anterior):
new FluentClass().Init<int>().With(-1).With(300).With(42);
Este problema es tan generalizado, con totalmente buenas intenciones, otro desarrollador una vez realmente cambió el nombre del método "Init" para indicar que ese método estaba haciendo el "trabajo real" del software.
Errores de lógica como estos son muy difíciles de detectar, y, por supuesto, se compila, porque es perfectamente aceptable llamar a un método con un valor de retorno y simplemente "pretender" que devuelve vacío. A Visual Studio no le importa si haces esto; su software aún se compilará y ejecutará (aunque en algunos casos creo que arroja una advertencia). Esta es una gran característica para tener, por supuesto. Imagine un método simple "InsertToDatabase" que devuelve el ID de la nueva fila como un entero: es fácil ver que hay algunos casos en los que necesitamos ese ID, y algunos casos en los que podríamos prescindir de él.
En el caso de esta pieza de software, definitivamente nunca hay ninguna razón para evitar esa función de "Guardar" al final de la cadena de métodos. Es una utilidad muy especializada, y la única ganancia proviene del último paso.
Quiero que el software de alguien falle en el nivel de compilador si llaman "Con()" y no "Guardar()".
Parece una tarea imposible por los medios tradicionales, pero es por eso que vengo a ustedes. ¿Existe un atributo que pueda usar para evitar que un método sea "lanzado al vacío" o algo así?
Nota: La forma alternativa de lograr este objetivo que ya ha sido sugerido para mí está escribiendo una serie de pruebas de unidad para hacer cumplir esta regla, y usar algo como http://www.testdriven.net ligarlos con el compilador. Esta es una solución aceptable, pero estoy esperando algo más elegante.
+1 para [valor de retorno] ;-) –
Disparo total en la oscuridad, no tengo conocimiento de que esto sea posible, pero tal vez .NET Code Contracts? Creo que están integrados en .NET 4.0 – Roly
Code Contracts no tiene un atributo o afirmación que requiera verificar el valor de retorno. – user7116