Страницы

Поиск по вопросам

понедельник, 9 декабря 2019 г.

Правильное использование видов коллекций в c#

#c_sharp #коллекции


Начал разбираться с основными видами коллекций в c#. Хотел бы спросить, когда можно
использовать те или иные интерфейсы коллекции при взаимодействии с методами.


Когда существует действительная необходимость возвращать IEnumerable из функции,
и, соответственно, когда принято передавать в качестве аргумента именно IEnumerable?
Является ли это хорошим решением (или даже хорошей практикой) принимать и/или возвращать
IReadOnlyList если данная функция не изменяет переданного списка и результат работы
функции также не должен быть изменен (т.е. коллекция, возвращаемая функцией, не должна
быть потом в программе изменена)?
Безопасней ли передавать и/или возвращать IReadOnlyList чем IEnumerable в конексте
возможного далее множественного итерирования?
С чем из IReadOnlyList или IEnumerable лучше взаимодействовать при работе/разработке
собственных LINQ функций при наличии условия неизменяемости передаваемой коллекции
и последующей неизменяемости возвращаемой?


Я понимаю, что под IReadOnly.... (List, Collection) подразумевается коллекция, доступная
только для чтения, т.е. нельзя в нее ничего добавить, однако внутреннее состояние объектов
в ней изменить можно.

Вопросы возникли после прочтения соответствующей статьи на Хабре: https://habr.com/ru/post/193774/
    


Ответы

Ответ 1



Передача коллекций в метод. Смысл кода должен быть понятен с первого взгляда. Используем чужую библиотеку. Видим метод: void Foo(List list) Что происходит внутри метода с этим списком? Изменяет ли автор его? Добавляет или удаляет элементы? Можем ли мы после вызова этого метода использовать эту коллекцию, рассчитывая, что она не изменилась? Конечно, всё это должно быть описано в документации к методу (библиотеке). Придётся читать... А если сигнатура будет: void Foo(IEnumerable enumerable) Тут сразу ясно, что коллекция внутри не может быть изменена. Можно даже не заглядывать в документацию. Естественно, если суть метода заключается именно в изменении коллекции, то используем ICollection, IList или прочий минимально необходимый тип. Код должен быть безопасным и надёжным. А что, если мы сомневаемся в авторах библиотеки? Ведь ничто не запрещает сделать так: void Foo(IEnumerable enumerable) { if (enumerable is List) { var list = (List)enumerable; list.Add(...); // ... } } Мы передали некий список в этот метод, где он - внезапно! - был изменён, на что мы не рассчитывали. А вот если используется сигнатура: void Foo(IReadOnlyCollection readOnlyCollection) то безопасность гарантирована. Конечно, вызывать нужно не так: Foo(list); а так: Foo(list.AsReadOnly()); В этом случае, приведение ридонли коллекции к любому другому типу вызовет исключение. Возврат коллекций из метода. На протяжении долгих лет в разных книгах, статьях, блогах, в документации и на форумах рекомендовалось возвращать конкретный тип. То есть: List GetNumbers() Внутри метода что-то делается, данные откуда-то получаются, как-то вычисляются и какую коллекцию наиболее удобно было использовать автору кода, ту он и возвращал. Если она совпала с ожиданиями того, кто будет использовать этот метод - замечательно - не нужно делать никаких преобразований. Например, можно сразу в этот список добавлять элементы. Но по неведомой причине в последнее время всё чаще стали возвращать абстрактные типы: IEnumerable и т. п. Без всяких объяснений. Причём внутри метода обычно что-то наподобине: IEnumerable GetNumbers() { List list = ... return list.AsEnumerable(); } Хорошо, если нам нужно просто перечислить элементы. Но если нужно менять коллекцию, придётся копировать данные: var list = GetNumbers().ToList(); Это и ненужный расход памяти и лишняя нагрузка на процессор. Кто-то может сказать: автор метода не хотел, чтобы его коллекцию меняли. Извините, но после того, как данные покинули метод, они уже не в его власти.

Ответ 2



Не помню точно где, прочел мысль вида: при построении API придерживается принципа принимайте максимально абстрактное, возвращай максимально конкретное. Этим я думаю и стоит руководствоваться при такого рода выборе. Основано на моих наблюдениях: IReadOnly* вообще стоит использовать для защиты инвариантов (и только при возврате значения) хорошо инкапсулированных классов (агрегатов, сущностей и объектов-значений). Пример того, о чем я говорю про инкапсуляция и контроль инвариантов: Возьмем объект "Машина", мы на ней можем ездить, можем закрывать ее...но нам так же хочется знать, какая резина на ее колесах...мы спрашиваем коллекцию ее колес, она отдает нам неизменяемый список (список, а не IEnumerable, потому что знает что нам будет логично обращаться к конкретному колесу по индексу)...он не модифицируемый, потому что иначе это позволит менять набор колес без ведома самой машины. Это не допустимо потому что машина контролирует инвариант вида 'производитель машины разрешает ставить колеса только диаметром 16-18"'. Опираясь на эти ограничения нам проще контролировать логику системы (нам не приходится опасаться что где то вне логики машины будет произведена модификация, которая нарушит инвариантность и согласованность объекта машины). В качестве аргументов принимать такой интерфейс бессмысленно вовсе, так как мутировать переданные объекты вообще моветон, только если метод явно не содержит это намерение в своем имени.

Комментариев нет:

Отправить комментарий