¿Son malos los métodos que modifican los parámetros del tipo de referencia?
-
05-07-2019 - |
Pregunta
He visto métodos como este:
public void Foo(List<string> list)
{
list.Add("Bar");
}
¿Es esta una buena práctica modificar parámetros en un método?
¿No sería esto mejor?
public List<string> Foo(List<string> list)
{
// Edit
List<string> newlist = new List<string>(list);
newlist.Add("Bar");
return newlist;
}
Parece que el primer ejemplo tiene efectos secundarios inesperados.
Solución
En el ejemplo que has dado, el primero me parece mucho más agradable que el segundo. Si veía un método que aceptaba una lista y también devolvía una lista, mi primera suposición sería que estaba devolviendo una nueva lista y no tocaba la que se le había dado. El segundo método, por lo tanto, es el que tiene efectos secundarios inesperados.
Siempre que sus métodos se nombren adecuadamente, hay poco peligro en modificar el parámetro. Considera esto:
public void Fill<T>(IList<T> list)
{
// add a bunch of items to list
}
Con un nombre como " Rellenar " puedes estar bastante seguro de que el método modificará la lista.
Otros consejos
Francamente, en este caso, ambos métodos hacen más o menos lo mismo. Ambos modificarán la List
que se pasó.
Si el objetivo es tener listas inmutables por dicho método, el segundo ejemplo debe hacer una copia de la List
que se envió y luego realizar la Add
operación en la nueva List
y luego devuélvala.
No estoy familiarizado con C # ni .NET, por lo que supongo que sería algo así como:
public List<string> Foo(List<string> list)
{
List<string> newList = (List<string>)list.Clone();
newList.Add("Bar");
return newList;
}
De esta forma, el método que llama al método Foo
obtendrá la nueva List
recién creada, y la List
original que se pasó no sería tocado.
Esto realmente depende del " contrato " de sus especificaciones o API, por lo que en los casos en que las List
s pueden modificarse, no veo ningún problema con el primer enfoque.
Estás haciendo exactamente lo mismo en ambos métodos, solo uno de ellos está devolviendo la misma lista.
Realmente depende de lo que estás haciendo, en mi opinión. Solo asegúrese de que su documentación sea clara sobre lo que está sucediendo. Escriba precondiciones y postcondiciones si le gustan ese tipo de cosas.
En realidad no es tan inesperado que un método que toma una lista como parámetro modifique la lista. Si desea un método que solo lea de la lista, usaría una interfaz que solo permita la lectura:
public int GetLongest(IEnumerable<string> list) {
int len = 0;
foreach (string s in list) {
len = Math.Max(len, s.Length);
}
return len;
}
Al usar una interfaz como esta, no solo prohíbe que el método cambie la lista, sino que también se vuelve más flexible, ya que puede usar cualquier colección que implemente la interfaz, como una matriz de cadenas, por ejemplo.
Algunos otros idiomas tienen una palabra clave const
que se puede aplicar a los parámetros para prohibir que un método los cambie. Como .NET tiene interfaces que puede usar para esto y cadenas que son inmutables, realmente no hay necesidad de parámetros const
.
La llegada de los métodos de extensión ha facilitado un poco el manejo de los métodos que introducen efectos secundarios. Por ejemplo, en su ejemplo se vuelve mucho más intuitivo decir
public static class Extensions
{
public static void AddBar(this List<string> list)
{
list.Add("Bar");
}
}
y llámalo con
mylist.AddBar();
lo que aclara que algo le está sucediendo a la lista.
Como se menciona en los comentarios, esto es más útil en las listas ya que las modificaciones a una lista pueden ser más confusas. En un objeto simple, tendería a modificar el objeto en su lugar.