Методы, которые изменяют параметры ссылочного типа, плохие?

StackOverflow https://stackoverflow.com/questions/621414

  •  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();

, который проясняет, что что-то происходит со списком.

Как уже упоминалось в комментариях, это наиболее полезно для списков, поскольку изменения в списке могут быть более запутанными. На простом объекте я хотел бы просто изменить объект на месте.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top