День 1489. #ЗаметкиНаПолях
Какой Интерфейс Коллекции Использовать? Начало
В этой серии постов о том, когда использовать какой тип коллекции и почему.
Общее руководство:
- Используйте максимально общие типы для аргументов. Типы аргументов приносят наибольшую пользу, когда они максимально универсальны, потому что метод может применяться к более широкому диапазону входных значений.
- Используйте максимально конкретные типы для возвращаемых значений. Возвращаемые типы обеспечивают наибольшую пользу, когда они максимально специфичны, поскольку конкретное возвращаемое значение предоставляет больше функциональных возможностей, чем универсальное.
Использование типов коллекций (и их интерфейсов) также должно соответствовать этому правилу. Вы также должны возвращать наиболее конкретный и принимать наиболее общий тип коллекции.
Вот упрощённая иерархия интерфейсов коллекций в .NET:
IEnumerable<T>
└- IQueryable<T>
└- IReadOnlyCollection<T>
| └- IReadonlyList<T>
└- ICollection<T>
└- Array
└- IList<T>
По рекомендации нужно возвращать самый конкретный тип. В этом иерархическом дереве есть несколько листьев:
- IQueryable
- IList
- Array
- IReadOnlyList
1. IQueryable — дырявая абстракция
Этот интерфейс позволяет запрашивать базу данных с помощью выражений LINQ так же, как коллекцию в памяти. Разница в том, что выражения LINQ, которые работают с IQueryable, выполняются в БД, а не в памяти. ORM, такие как EF Core и NHibernate, реализуют провайдера IQueryable, который преобразует выражения LINQ в запросы SQL. IQueryable можно рассматривать как абстракцию, позволяющую одинаково работать с разными источниками данных.
IQueryable особенно популярен в репозиториях, где вы можете ввести метод GetAll, например:
// Student repository
public IQueryable<Student> GetAll() =>
_context.Set<Student>();
А затем добавлять фильтры в клиентском коде.
Такой подход на первый взгляд кажется логичным. Клиентский код может строить фильтры поверх IQueryable в зависимости от сценария. Но IQueryable является дырявой абстракцией, которая требует от вас знания деталей реализации, которые она должна абстрагировать. Вы должны знать, какие выражения LINQ могут быть преобразованы в SQL, а какие нет. Таким образом, использование IQueryable как части общедоступного API вводит в заблуждение. Создаётся ложное впечатление, что вы можете применить любое выражение поверх этого возвращаемого значения.
То же самое верно, когда вы принимаете выражение в качестве аргумента в репозитории, например:
public IQueryable<Student> GetAll(
Expression<Func<Student, bool>> predicate) =>
_context.Set<Student>().Where(predicate);
Решение состоит в том, чтобы не использовать IQueryable (или выражения) как часть общедоступного API репозитория. Вы можете использовать его внутри, потому что репозиторий знает, с каким ORM он работает и его ограничения. Но эти знания не должны пересекать границы репозитория, т.е. не должны утекать к клиентам репозитория.
Продолжение следует…
Источник: https://enterprisecraftsmanship.com/posts/which-collection-interface-to-use/