Это плохая форма для возврата массивов в С#? Должен ли я возвращать List <T>?
У меня есть функция, которая возвращает переменное число элементов, должен ли я возвращать массив или список? Размер "коллекции" не изменяется после возврата, т.е. Для всех целей коллекция неизменна. Я бы подумал просто вернуть массив, но некоторые люди сказали, что не возвращают массивы с переменным размером из функции, так как это "плохая форма". Не знаете, почему?
Имеет ли значение, что это должно быть совместимо с .NET 2.0?
Ответы
Ответ 1
Плохая форма для возврата массивов, если не нужна, и особенно для возврата List<T>
.
Обычно вам нужно вернуть IEnumerable<T>
или IList<T>
.
Если вашему пользователю просто нужно будет запускать каждый элемент, IEnumerable<T>
предоставит эту возможность. Он также позволяет потенциально реализовать подпрограмму (сейчас или позже) с использованием отложенного выполнения.
Если вашему пользователю нужно обращаться к элементам по индексу, верните IList<T>
. Это обеспечивает все преимущества массивов, но дает вам большую гибкость в вашей реализации. Вы можете реализовать его как массив, список или другую коллекцию, которая реализует IList<T>
, и вам не нужно преобразовывать/копировать в массив.
Ответ 2
В одном из мнений я часто вижу предложение вернуться либо IList<T>
, либо ReadOnlyCollection<T>
. ОК, чтобы вернуть их, если у вас есть один из них - оба могут быть назначены непосредственно на IEnumerable<T>
(они работают напрямую с любыми методами LINQ). Следует отметить, что ReadOnlyCollection<T>
- очень легкий тип, который может обернуть любой IList<T>
.
Ответ 3
Развертывание ответа Рида.
Эрик Липперт сделал замечательную запись в блоге по этому вопросу. Вероятно, он получил наиболее подробный ответ
Ответ 4
Как вы, несомненно, видели ответы в этой теме, мнения по этому вопросу широки.
В общем, мои мысли таковы:
Если я возвращаю список, размер которого постоянный, и я не хочу, чтобы вызывающий абонент мог изменять мои данные (что составляет 99% времени), я верну его ReadOnlyCollection<T>
. Это дает мне неизменность на стороне клиента без необходимости удвоения (или тройного или любого другого) объема памяти моих данных при создании нового списка или массива.
Я смущаюсь сказать, что "вы всегда должны возвращать IEnumerable
или IEnumerable<T>
". Хотя в некоторых случаях это, безусловно, подходит (и этих случаев не так много), легкий характер интерфейса IEnumerable
значительно ограничивает вашу функциональность (ни один индексный поиск не является самым большим), а во многих случаях основным источником в любом случае данные будут массивом, даже если это a List<T>
.
Дополнительная опасность возврата IEnumerable
исходит из ленивой практики простого возврата внутреннего списка в контексте этого интерфейса. Выполнение этого предоставляет вам метод вызова, злоупотребляющий этим ярлыком, возвращая его к более надежному типу коллекции. Хороший, защитный программист этого не сделает.
Самый низкий объем памяти происходит от использования ReadOnlyCollection
, построенного из List
. A ReadOnlyCollection
все еще подвергает вас опасности из-за злоупотреблений на основе отражения и фиксирует ссылку на изменяемый список, но это немного случайный случай.
Ответ 5
Если размер коллекции не изменится после возврата IEnumerable<T>
, это будет моим выбором, так как вызывающий может затем использовать методы расширения LINQ со списком сразу.
Ответ 6
Легко получить массив из IList, если массив нужен вам, но обратное неверно. Поэтому возвращение IList предпочтительнее.
Ответ 7
Верните IEnumerable
, если вам не нужно:
- Доступ Count - затем верните
IReadOnlyCollection
.
- изменить элементы, а Count - меньше ArrayList.Capacity - затем вернуть массив.
- добавить новые элементы - затем вернуть список.
- некоторые микрооптимизации - это бенчмарк. Например. foreach over array быстрее, чем над списком
BenchmarkDotNet = v0.10.8, OS = Windows 10 Redstone 1 (10.0.14393)
Процессор = Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount = 4
Частота = 3233538 Гц, разрешение = 309,2588 нс, таймер = TSC [Хост]: Clr 4.0.30319.42000, 64 бит RyuJIT-v4.6.1648.0 Clr: Clr 4.0.30319.42000, 64 бит RyuJIT-v4.6.1648.0 Core:.NET Core 4.6.25211.01, 64bit RyuJIT
Method | Job | Runtime | Mean | Error | StdDev | Min | Max | Median | Rank | Allocated |
---------- |----- |-------- |-----------:|-----------:|----------:|-----------:|-----------:|-----------:|-----:|----------:|
TestList | Clr | Clr | 5,153.3 ns | 34.002 ns | 31.806 ns | 5,119.2 ns | 5,213.4 ns | 5,135.9 ns | 3 | 0 B |
TestArray | Clr | Clr | 730.1 ns | 6.962 ns | 6.512 ns | 722.4 ns | 743.9 ns | 729.5 ns | 2 | 0 B |
TestList | Core | Core | 5,188.4 ns | 102.816 ns | 96.174 ns | 5,070.3 ns | 5,342.6 ns | 5,185.6 ns | 3 | 0 B |
TestArray | Core | Core | 709.0 ns | 6.126 ns | 5.730 ns | 700.8 ns | 718.6 ns | 708.1 ns | 1 | 0 B |
Код:
[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
[ClrJob, CoreJob]
[HtmlExporter, MarkdownExporter]
[MemoryDiagnoser]
public class BenchmarkForEach
{
List<string> testData = new List<string>();
string[] testArray;
public BenchmarkForEach()
{
for(int i=0;i<1000;i++)
{
testData.Add(i.ToString());
}
testArray = testData.ToArray();
}
[Benchmark]
public int TestList()
{
var x = 0;
foreach(var i in testData)
{
x += i.Length;
}
return x;
}
[Benchmark]
public int TestArray()
{
var x = 0;
foreach (var i in testArray)
{
x += i.Length;
}
return x;
}
}