Подтвердить что ты не робот

Должны ли параметры/возвраты коллекций IEnumerable <T> или T []?

Поскольку я включаю мышление Linq, я все больше склоняюсь к тому, чтобы передавать коллекции с помощью типового типа IEnumerable<T>, который, как представляется, является основой большинства операций Linq.

Однако мне интересно, с поздней оценкой типового типа IEnumerable<T>, если это хорошая идея. Имеет ли смысл использовать типовой тип T[]? IList<T>? Или что-то еще?

Изменить: Комментарии ниже довольно интересны. Одна вещь, которая до сих пор не решена, по-видимому, является проблемой безопасности потоков. Если, например, вы принимаете аргумент IEnumerable<T> для метода и его перечисляете в другом потоке, тогда, когда этот поток пытается получить к нему доступ, результаты могут отличаться от результатов, которые должны были быть переданы. Хуже того, пытаясь дважды перечислить IEnumerable<T> - я считаю, что это исключение. Разве мы не должны стремиться к тому, чтобы наши методы были безопасными?

4b9b3361

Ответ 1

Я прошел фазу прохождения вокруг T[], и, чтобы сократить длинную историю, это боль в задней части. IEnumerable<T> намного лучше

Однако мне интересно, с поздней оценкой типичного типа IEnumerable, если это хорошая идея. Имеет ли смысл использовать типовой тип T []? IList? Или что-то еще

Поздняя оценка именно поэтому IEnumerable настолько хороша. Вот пример рабочего процесса:

IEnumerable<string> files = FindFileNames();
IEnumerable<string> matched = files.Where( f => f.EndsWith(".txt") );
IEnumerable<string> contents = matched.Select( f => File.ReadAllText(f) );
bool foundContents = contents.Any( s => s.Contains("orion") );

Для нетерпеливых это получает список имен файлов, отфильтровывает файлы .txt, а затем устанавливает foundContents в true, если в каком-либо из текстовых файлов содержится слово orion.

Если вы пишете код с помощью IEnumerable, как указано выше, вы будете загружать только каждый файл по мере необходимости. Использование вашей памяти будет довольно низким, и если вы согласитесь на первый файл, вы не должны смотреть на любые последующие файлы. Это здорово.

Если вы написали этот точный код с использованием массивов, вы в конечном итоге загрузите все содержимое файла вперед, и только тогда (если у вас осталось какое-либо ОЗУ) будет ли кто-нибудь из них сканироваться. Надеюсь, это поможет понять, почему ленивые списки настолько хороши.

Одна вещь, которая не получила ответа, кажется, является проблемой безопасности потоков. Если, например, вы принимаете аргумент IEnumerable<T> для метода и его перечисляете в другом потоке, тогда, когда этот поток пытается получить к нему доступ, результаты могут отличаться от результатов, которые должны были быть переданы. Хуже того, пытаясь дважды перечислить IEnumerable<T> - я считаю, что это исключение. Разве мы не должны стремиться к тому, чтобы наши методы были безопасными?

Безопасность нитей - гигантская красная селедка.

Если вы использовали массив, а не перечислимый, похоже, что он должен быть более безопасным, но это не так. Большую часть времени, когда люди возвращают массивы объектов, они создают новый массив, а затем помещают в него старые объекты. Если вы вернете этот массив, тогда эти исходные объекты могут быть изменены, и вы получите именно те проблемы, которые вы пытаетесь избежать.

Частичное решение состоит в том, чтобы не возвращать массив исходных объектов, а массив новых или клонированных объектов, поэтому другие потоки не могут получить доступ к исходным. Это полезно, однако нет причин, по которым решение IEnumerable тоже не может этого сделать. Один из них не более потокобезопасен, чем другой.

Ответ 2

По большей части вам не следует передавать массивы (T[]) на интерфейсы public. См. Сообщение в блоге Eric Lippert "" Массивы считаются несколько вредными" для деталей.

Исключение составляют методы, которые принимают массив params, поскольку он должен быть массивом. (Надеемся, что будущая версия позволит "params IList<s> foo" в сигнатурах методов.)

Для internal членов, сделайте все, что вам нравится; у вас есть контроль над обеими сторонами интерфейса.

Ответ 3

Типы вывода должны быть как можно более конкретными, типы ввода должны быть как можно более свободными.

Итак верните T[], но возьмите IEnumerable<T> в качестве ввода.

Вы должны вернуть то, что имеет смысл для метода. Если нужно сделать дополнительную работу для преобразования в другой тип, подумайте о том, чтобы просто отбросить это и вернуть все, что у вас есть внутри, до тех пор, пока вы не возвращаете ссылки на внутренние структуры данных, которые не следует изменять вне вашего собственного кода.

Ответ 4

"Разве мы не должны стремиться к тому, чтобы наши методы были безопасными?"

Мы должны быть преднамеренными о способностях нашего кода.

Просто потому, что метод не является потокобезопасным, это не значит, что это ошибка, просто вам нужно знать этот факт, прежде чем пытаться использовать его в многопоточной программе. Стремитесь ли вы сделать Main() threadafe? Имеет ли проблема безопасности потоков в однопоточной программе?

В любом случае, я не думаю, что действительно имеет смысл сказать: "Foo - это потокобезопасный", только "Foo имеет следующие характеристики, которые важны в многопоточном контексте".

", пытаясь перечислить IEnumerable дважды - я считаю, что это исключение.

Это было бы плохо. К счастью, вы можете проверить это, вместо того, чтобы расширять FUD.

Я думаю, что вы спрашиваете: "Не следует ли мне возвращать копию моей коллекции вместо ссылки на внутреннюю коллекцию членов, чтобы вызывающие меня не изменяли мои данные"? И ответ - безоговорочный " возможно". Существует много способов решения этой проблемы.

Ответ 5

Я бы выбрал между IList и IEnumerable и вообще не рассматривал массив.

Обратите внимание, что IEnumerable не имеет свойства Count или Length, кроме как метод расширения, в то время как массив и IList делают. Итак, я бы основал свое решение по этому поводу:

Если возвращаемое значение имеет известное количество элементов → IList или массив (если необходимо) в противном случае IEnumberable

Ответ 6

Однако мне интересно, с поздней оценкой типичного типа IEnumerable, если это хорошая идея. Имеет ли смысл использовать типовой тип T []? IList? Или что-то еще?

Вы должны быть осторожны с терминами, которые вы используете. Помните о различии между типом ссылки и типом экземпляра.

IEnumerable of T только говорит: "Я могу перебирать эту коллекцию, вызывая GetEnumerator. Когда я это делаю, каждый элемент может называться T." IEnumerable of T ничего не говорит о ленивой оценке.

Рассмотрим эти три объявления:

//someInts will be evaluated lazily
IEnumerable<int> someInts = Enumerable.Range(0, 100);

//oddInts will be evaluated lazily
IEnumerable<int> oddInts = someInts.Where(i => i % 2 == 1);

//evenInts will be evaluated eagerly
IEnumerable<int> evenInts = someInts.Except(oddInts).ToList();

Несмотря на то, что ссылочный тип всех трех элементов IEnumerable для int, оценивается только evenInts (поскольку ToList перечисляет свою цель и возвращает экземпляр List).

Что касается рассматриваемого вопроса, то, что я обычно делаю, это относиться к моим вызывающим абонентам как ожидающим с нетерпением оценкой Enumerable, поэтому я делаю это:

public IEnumerable<int> GetInts()
{
  ...
  return someInts.ToList()
}

Ответ 7

Реализовав интерпретируемый язык программирования, написанный на С#, мне также нужно было выбрать между возвращаемым значением объекта type [] и List для Linq-подобных функций как map и filter. В конце я выбираю ленивый вариант списка Lisp. Существует конструктор списка с аргументом IEnumerable: z = new LazyList (...). Всякий раз, когда программа ссылается на одно из свойств LazyList (head, tail или isempty), перечислимый оценивается всего за один шаг (голова и неясно становятся определенными, хвост становится еще одним ленивым списком). Преимущество лазилиста над IEnumerable заключается в том, что первый позволяет рекурсивные алгоритмы и не должен (должен) страдать от нескольких оценок.

Ответ 8

Вы всегда можете сделать это для безопасности потоков, он, вероятно, будет работать лучше во многих случаях, так как нет блокировки коллекции (ее копия). Обратите внимание, это означает, что вы выполняете LINQ или foreach не в коллекции, а в члене.

  public IEnumerable<ISubscription> this[string topic] {
            get  { 
                rwlock.EnterReadLock();
                try {
                    return subscriptionsByTopic[GetTopic(topic)].ToArray<ISubscription>();              
                    //thread safe
                }
                finally {
                    rwlock.ExitReadLock();
                }
            }
        }

Также не используйте IEnumerable для SOA/многоуровневых приложений, поскольку вы не можете сериализовать интерфейсы (без большого количества боли).WCF лучше работает с List.