There is no such thing as "further filtering".
Filtering collections is usually done using the IEnumerable.Where
extension method, which is defined for the IEnumerable
interface. And since IList
inherits from IEnumerable
, you can call Where
on both interfaces (calling Where
on a IList
actually calls the IEnumerable.Where
extension method). So, in both cases, the same base method is called, and the type of the resulting value will be an IEnumerable
(not an IList
when applied to a list). This might be the source of confusion ("you cannot filter the IList
further since you don't have it anymore?"), but nothing stops you from filtering the resulting IEnumerable<T>
again, or even writing your own extension method which would create a new List
on each call.
The post linked to in the question is of low quality and shouldn't be trusted.
For detailed explanation, see below.
You can filter elements from both interfaces pretty much the same, although you will generally use IEnumerable
extension methods (i.e. LINQ) in both cases, since IList
inherits from IEnumerable
. And you can chain as many Where
statements as you like in both cases:
// `items` is an `IEnumerable<T>`, so we can call the `Where` extension method.
// Each call creates a new instance, and keeps the previous one unmodified.
IEnumerable<T> items = GetEnumerableItems();
var filteredItems = items
.Where(i => i.Name == "Jane") // returns a new IEnumerable<T>
.Where(i => i.Gender == "Female") // returns a new IEnumerable<T>
.Where(i => i.Age == 30) // returns a new IEnumerable<T>
// `list` is an `IList<T>`, which also inherits from `IEnumerable<T>`.
// Calling `Where` on a list will also not modify the original list.
IList<T> list = GetEnumerableItems();
var filteredList = list
.Where(i => i.Name == "John") // returns a new IEnumerable<T>
.Where(i => i.Gender == "Male") // returns a new IEnumerable<T>
.Where(i => i.Age == 30) // returns a new IEnumerable<T>
.ToList(); // returns a new List<T> (optional)
Googling for the term returns several articles mentioning it (like this, or this), they all seem to copy the same source, seems like plagiarism without actual reasoning behind it. The only thing that can come to my mind is that applying Where
to an IEnumerable<T>
returns a new (filtered) IEnumerable<T>
, to which you can again apply Where
(filter it "further"). But that is really vague, since applying Where
to an IList<T>
will not prevent you from filtering it, even though the resulting interface is an IEnumerable<T>
. As mentioned in comments, it might be worth mentioning that the List<T>
class, as a concrete implementation of IList<T>
, exposes a FindAll
method which returns a new filtered concrete List<T>
(and can be "further filtered"), but that's not a part of IList<T>
.
The main difference between repeatedly filtering an IEnumerable<T>
and filtering a list into a new list (e.g. using FindAll
), is that the latter needs to create a new List<T>
instance in each step, while IEnumerable<T>
uses deferred execution and doesn't take extra memory apart from storing some tiny state information for each Where
call. And again, just to avoid confusion, if you call Where
on a List<T>
, you still get the benefits of IEnumerable<T>
laziness.
Actual differences:
IList
(or actually IList<T>
, which I am presuming you're referring to) represents a collection of objects that can be individually accessed by index. This means that you can efficiently (in O(1)
time) get the value of an object at a certain location, as well as list's length. The "bad thing" is (presuming that it's implemented as a List<T>
under the hood), that this means that you need to keep the entire collection in memory.
The "only thing" IEnumerable
(i.e. its generic counterpart IEnumerable<T>
) can do is to iterate over (zero or more) items. It doesn't have a notion of an index (you cannot "jump" to an index, without actually iterating, or skipping, all items before that item). And you also cannot get the length efficiently in the general case, without actually counting items every time. On the other hand, an IEnumerable
is lazy-evaluated, meaning that its elements don't have to exist in memory until they are about to be evaluated. It can wrap a database table underneath, with billions of rows, fetched from the disk as you iterate it. It can even be an infinite collection.
IEnumerable further filtering
are very good. It's like someone read something and summarised it badly, and then a lot of other people copied it without really understanding either. – GillimyList.Take(10)
to get Top 10 items in list e.g. – EleneeleniIList<T>
(or anIReadOnlyList<out T>
) is also, by inheritance, anIEnumerable<out T>
, so it "supports" everything theIEnumerable<>
supports. "Further filtering" is not clear; to me it sounds like Linq'sWhere
following anotherWhere
, but that makes no sense in the context. The only thingIEnumerable<out T>
has thatList<T>
lacks, is the covariance ("out
"). UseIReadOnlyList<out T>
if you want covariance and access by index. – Corderovoid RunThroughAnimals(IEnumerable<Animal> animals) { ... }
, I can call your method with a list of giraffes,List<Giraffe>
, because of covariance. With a methodvoid RunThroughAnimals(IList<Animal> animals) { ... }
, I cannot come in with aList<Giraffe>
. – Cordero