What is the preferred (exponential and readable) way to bind IEnumerable <T> extension methods?
If I try to filter the results at multiple levels of an IEnumerable<T>
graphic, is there a preferred way to bind extension methods for this?
I am open to any method of extending and using lambda, but I would prefer not to use LINQ syntax to stay consistent with the rest of the code base.
Are you better off pushing filtering on the selector
the SelectMany()
method or just on a chain of another Where()
method? Or is there a better solution?
How do I find the best option? In this case, everything is available directly in memory. Obviously, both examples below currently give the same correct results; I'm just looking for a reason for one or the other (or another option) that would be preferable.
public class Test { // I want the first chapter of a book that exactly 42 pages, written by // an author whose name is Adams, from a library in London. public Chapter TestingIEnumerableTExtensionMethods() { List<Library> libraries = GetLibraries(); Chapter chapter = libraries .Where(lib => lib.City == "London") .SelectMany(lib => lib.Books) .Where(b => b.Author == "Adams") .SelectMany(b => b.Chapters) .First(c => c.NumberOfPages == 42); Chapter chapter2 = libraries .Where(lib => lib.City == "London") .SelectMany(lib => lib.Books.Where(b => b.Author == "Adams")) .SelectMany(b => b.Chapters.Where(c => c.NumberOfPages == 42)) .First(); }
And here is an example graph of objects:
public class Library { public string Name { get; set; } public string City { get; set; } public List<Book> Books { get; set; } } public class Book { public string Name { get; set; } public string Author { get; set; } public List<Chapter> Chapters { get; set; } } public class Chapter { public string Name { get; set; } public int NumberOfPages { get; set; } }
Most likely, it depends on your LINQ implementation. LinqToSql will behave differently than in-memory filtering. The order of sentences should affect performance depending on what data is used, since naive implementations will filter more records earlier in the sequence, which means less work for later methods.
For your two examples, I would suggest that the difference in performance is insignificant and would contribute to the former, since it allows you to simplify the modification of each sentence independently of the others.
As for determining the best option, it will be the same as everything else: measure.
I assume that the first expression you have will be slightly, but not significantly faster. To really determine whether it’s faster or faster, you need time with a profiler or stopwatch.
In any case, readability is not very dependent. I prefer the first approach, as it has fewer breeding levels. It all depends on your personal preferences.
This may give you a different angle, although it is rather a matter of style ...
I sometimes find myself doing something like this ...
return libraries.Filter( l => l.City == "", l => l.Books, b => b.Author == "Adams", b => b.Chapters, c => c.NumberOfPages == 42 );
... where you can guess what is extensive, something like ...
public static IEnumerable<TC> Filter<TL, TB, TC>(this IEnumerable<TL> list, Func<TL, bool> whereLs, Func<TL, IEnumerable<TB>> selectBs, Func<TB, bool> whereBs, Func<TB, IEnumerable<TC>> selectCs, Func<TC, bool> whereCs ) { return list .Where(whereLs) .SelectMany(selectBs) .Where(whereBs) .SelectMany(selectCs) .Where(whereCs); }
... or....
... { return list .Where(whereLs) .SelectMany(l => selectBs(l).Where(whereBs)) .SelectMany(b => selectCs(b).Where(whereCs)); }
And there are many combinations / options, depending on what you have, how you like your code (abstract it a little more or “capture”, “parameterize”, for example, PerCityAuthorPages(_city, _author, _numPages);
etc .)
... Basically, I don’t like to have everything “Where”, “Select”, etc., and for me it is not so readable (also). While with the "short form" it is very clear what it is, where, choose, etc., And it is very "short" and much less characters.
In addition, you can cancel the decision on Where / Select options for later versions (do one or the other based on the needs of the provider)
And @Telastyn is absolutely right, LINQ providers, for example. if you look at some implementation code,
with decreasing expressions, etc.
are quite non-deterministic (i.e. from provider to provider) in such a way that they can complete the mapping, for example. SQL
although this should display the same thing in most that I think.