Os métodos que modificam os parâmetros do tipo de referência são ruins?
-
05-07-2019 - |
Pergunta
Eu já vi métodos como este:
public void Foo(List<string> list)
{
list.Add("Bar");
}
Esta é uma boa prática para modificar parâmetros em um método?
Isso não seria melhor?
public List<string> Foo(List<string> list)
{
// Edit
List<string> newlist = new List<string>(list);
newlist.Add("Bar");
return newlist;
}
Parece que o primeiro exemplo tem efeitos colaterais inesperados.
Solução
No exemplo que você deu, o primeiro me parece muito melhor do que o segundo. Se eu visse um método que aceitasse uma lista e também devolvesse uma lista, minha primeira suposição seria que ela estava retornando uma nova lista e não tocando a que recebeu. O segundo método, portanto, é o com efeitos colaterais inesperados.
Desde que seus métodos sejam nomeados adequadamente, haja pouco perigo na modificação do parâmetro. Considere isto:
public void Fill<T>(IList<T> list)
{
// add a bunch of items to list
}
Com um nome como "preenchimento", você pode ter certeza de que o método modificará a lista.
Outras dicas
Francamente, neste caso, ambos os métodos fazem mais ou menos a mesma coisa.Ambos modificarão o List
que foi passado.
Se o objetivo é ter listas imutáveis por tal método, o segundo exemplo deve fazer uma cópia do List
que foi enviado e, em seguida, execute o Add
operação no novo List
e depois devolva isso.
Não estou familiarizado com C # nem .NET, então meu palpite seria algo como:
public List<string> Foo(List<string> list)
{
List<string> newList = (List<string>)list.Clone();
newList.Add("Bar");
return newList;
}
Dessa forma, o método que chama o Foo
método obterá o recém-criado List
devolvido e o original List
que foi passado não seria tocado.
Isso realmente depende do "contrato" de suas especificações ou API, portanto, nos casos em que List
s podem apenas ser modificados, não vejo problema em seguir com a primeira abordagem.
Você está fazendo exatamente a mesma coisa nos dois métodos, apenas um deles está retornando a mesma lista.
Realmente depende do que você está fazendo, na minha opinião. Apenas certifique -se de que sua documentação esteja clara sobre o que está acontecendo. Escreva pré-condições e pós-condições, se você gosta desse tipo de coisa.
Na verdade, não é tão inesperado que um método que leva uma lista como o parâmetro modifique a lista. Se você deseja um método que apenas lê na lista, você usaria uma interface que apenas permite a leitura:
public int GetLongest(IEnumerable<string> list) {
int len = 0;
foreach (string s in list) {
len = Math.Max(len, s.Length);
}
return len;
}
Usando uma interface como essa, você não proíbe apenas o método de alterar a lista, ela também fica mais flexível, pois pode usar qualquer coleção que implemente a interface, como uma matriz de string, por exemplo.
Algumas outras línguas têm um const
Palavra -chave que pode ser aplicada aos parâmetros para proibir um método de alterá -los. Como .NET tem interfaces que você pode usar para isso e strings que são imutáveis, não há realmente uma necessidade de const
parâmetros.
O advento dos métodos de extensão tornou um pouco mais fácil lidar com métodos que introduzem efeitos colaterais. Por exemplo, no seu exemplo, fica muito mais intuitivo dizer
public static class Extensions
{
public static void AddBar(this List<string> list)
{
list.Add("Bar");
}
}
e chame com
mylist.AddBar();
O que deixa mais claro que algo está acontecendo com a lista.
Como mencionado nos comentários, isso é mais útil em listas, pois as modificações em uma lista podem tender a ser mais confusas. Em um objeto simples, eu tenderia a modificar o objeto no lugar.