Методы, которые изменяют параметры ссылочного типа, плохие?
-
05-07-2019 - |
Вопрос
Я видел такие методы:
public void Foo(List<string> list)
{
list.Add("Bar");
}
Это хорошая практика для изменения параметров в методе?
Разве это не было бы лучше?
public List<string> Foo(List<string> list)
{
// Edit
List<string> newlist = new List<string>(list);
newlist.Add("Bar");
return newlist;
}
Такое ощущение, что первый пример имеет неожиданные побочные эффекты.
Решение
В приведенном вами примере первое мне кажется намного лучше, чем второе. Если бы я увидел метод, который принимал список, а также возвращал список, мое первое предположение состояло бы в том, что он возвращал новый список, а не касался того, который ему дали. Поэтому второй метод - это метод с неожиданными побочными эффектами.
До тех пор, пока ваши методы имеют соответствующие имена, изменение параметра не представляет особой опасности. Учтите это:
public void Fill<T>(IList<T> list)
{
// add a bunch of items to list
}
С именем, подобным " Fill " Вы можете быть уверены, что метод изменит список.
Другие советы
Честно говоря, в этом случае оба метода делают более или менее одно и то же. Оба изменят Список
, который был передан.
Если цель состоит в том, чтобы списки были неизменяемыми с помощью такого метода, во втором примере необходимо создать копию отправленного List
, а затем выполнить Add
операция над новым List
, а затем вернуть его.
Я не знаком ни с C #, ни с .NET, так что я думаю, что-то вроде:
public List<string> Foo(List<string> list)
{
List<string> newList = (List<string>)list.Clone();
newList.Add("Bar");
return newList;
}
Таким образом, метод, который вызывает метод Foo
, вернет вновь созданный List
и исходный List
, который был передан в не будет затронут.
Это действительно зависит от " контракта " ваших спецификаций или API, поэтому в случаях, когда List
можно просто изменить, я не вижу проблем с первым подходом.
Вы делаете одно и то же в обоих методах, только один из них возвращает один и тот же список.
Это действительно зависит от того, что ты делаешь, по моему мнению. Просто убедитесь, что в вашей документации ясно, что происходит. Напишите предварительные условия и постусловия, если вам нравятся подобные вещи.
На самом деле не так уж неожиданно, что метод, который принимает список в качестве параметра, изменяет список. Если вам нужен метод, который читает только из списка, вы должны использовать интерфейс, который позволяет только чтение:
public int GetLongest(IEnumerable<string> list) {
int len = 0;
foreach (string s in list) {
len = Math.Max(len, s.Length);
}
return len;
}
Используя такой интерфейс, вы не только запрещаете методу изменять список, он также становится более гибким, поскольку он может использовать любую коллекцию, реализующую интерфейс, например, строковый массив.
В некоторых других языках есть ключевое слово const
, которое можно применять к параметрам, чтобы запретить методу изменять их. Поскольку в .NET есть интерфейсы, которые вы можете использовать для этого, и неизменяемые строки, в действительности нет необходимости в параметрах const
.
С появлением методов расширения стало немного легче иметь дело с методами, которые вызывают побочные эффекты. Например, в вашем примере это становится намного более интуитивным, чтобы сказать
public static class Extensions
{
public static void AddBar(this List<string> list)
{
list.Add("Bar");
}
}
и позвони с помощью
mylist.AddBar();
, который проясняет, что что-то происходит со списком. Р>
Как уже упоминалось в комментариях, это наиболее полезно для списков, поскольку изменения в списке могут быть более запутанными. На простом объекте я хотел бы просто изменить объект на месте. Р>