LINQ "let"
-
Learned something new today. I was googling for CRC algorithms and came across this nifty site[^] and started perusing it more generally, then realized I had no idea about let clauses in query expressions![^] Geez, I've been using LINQ for a while now, and didn't know about that. :doh: Marc
Imperative to Functional Programming Succinctly Contributors Wanted for Higher Order Programming Project! Learning to code with python is like learning to swim with those little arm floaties. It gives you undeserved confidence and will eventually drown you. - DangerBunny
Hi Marc, I first became aware of the 'Let and 'Into Linq operators through this 2011 CP article (which I down-voted for "lack of original content"): [^]. I have never used them :) because I have never invested the energy to learn to use the "fuller" query syntax (my bad). Your comment makes me wonder what I am missing (other than motivation). cheers, Bill p.s. the Microsoft example of 'Let you cite imho goes to a lot trouble do this:
string[] strings =
{
"A penny saved is a penny earned.",
"The early bird catches the worm.",
"The pen is mightier than the sword."
};string vowels = "aeiou";
List<string> vowelstartwords = String.Join(" ", strings)
.Split(' ')
.Distinct()
.Where(word => vowels.Contains(Char.ToLower(word[0])))
.ToList();That example, taken as a programming challenge, interests me: it leaves me wondering if it could be significantly improved in terms of memory use and execution time, and if the Linq code using 'Let would, in fact, improve those usage parameters.
«There is a spectrum, from "clearly desirable behaviour," to "possibly dodgy behavior that still makes some sense," to "clearly undesirable behavior." We try to make the latter into warnings or, better, errors. But stuff that is in the middle category you don’t want to restrict unless there is a clear way to work around it.» Eric Lippert, May 14, 2008
-
Rob Philpott wrote:
Is there anything you can do in one syntax that you can't in the other?
Possibly
Cast<T>
For example:T record = mappedRecords[typeof(T)].Cast().Where(r => r.Row == row).Single();
But I'm not sure, I haven't seen any examples using query syntax. I suppose the point though is, you should cast before you query. :rolleyes: Marc
Imperative to Functional Programming Succinctly Contributors Wanted for Higher Order Programming Project! Learning to code with python is like learning to swim with those little arm floaties. It gives you undeserved confidence and will eventually drown you. - DangerBunny
The
Cast<T>
part isn't a problem for query syntax - you just declare the type on the variable in thefrom
clause:from T mappedRecord in mappedRecord[typeof(T)]
...But there's no query syntax keyword for
Single
, so you still have to call that as a method:T record = (from T mappedRecord in mappedRecords[typeof(T)]
where mappedRecord.Row == row
select mappedRecord).Single();I think the method chaining syntax is much cleaner - especially if you use the overload of
Single
to eliminate theWhere
call:T record = mappedRecords[typeof(T)].Cast<T>().Single(r => r.Row == row);
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
-
Hi Marc, I first became aware of the 'Let and 'Into Linq operators through this 2011 CP article (which I down-voted for "lack of original content"): [^]. I have never used them :) because I have never invested the energy to learn to use the "fuller" query syntax (my bad). Your comment makes me wonder what I am missing (other than motivation). cheers, Bill p.s. the Microsoft example of 'Let you cite imho goes to a lot trouble do this:
string[] strings =
{
"A penny saved is a penny earned.",
"The early bird catches the worm.",
"The pen is mightier than the sword."
};string vowels = "aeiou";
List<string> vowelstartwords = String.Join(" ", strings)
.Split(' ')
.Distinct()
.Where(word => vowels.Contains(Char.ToLower(word[0])))
.ToList();That example, taken as a programming challenge, interests me: it leaves me wondering if it could be significantly improved in terms of memory use and execution time, and if the Linq code using 'Let would, in fact, improve those usage parameters.
«There is a spectrum, from "clearly desirable behaviour," to "possibly dodgy behavior that still makes some sense," to "clearly undesirable behavior." We try to make the latter into warnings or, better, errors. But stuff that is in the middle category you don’t want to restrict unless there is a clear way to work around it.» Eric Lippert, May 14, 2008
I'd be inclined to avoid joining the strings just to split them again. I'd also be inclined to use an array of
char
, rather than searching a string - although I doubt it would make much difference. You've also added aDistinct
and aToList
which weren't in the original example. :)char[] vowels = { 'a', 'e', 'i', 'o', 'u' };
IEnumerable<string> vowelStartWords = strings
.SelectMany(sentence => sentence.Split(' '))
.Where(word => Array.IndexOf(vowels, char.ToLower(word[0])) != -1)
;
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
-
Learned something new today. I was googling for CRC algorithms and came across this nifty site[^] and started perusing it more generally, then realized I had no idea about let clauses in query expressions![^] Geez, I've been using LINQ for a while now, and didn't know about that. :doh: Marc
Imperative to Functional Programming Succinctly Contributors Wanted for Higher Order Programming Project! Learning to code with python is like learning to swim with those little arm floaties. It gives you undeserved confidence and will eventually drown you. - DangerBunny
Marc, you probably know this by now, but
let
is just syntactic sugar for theSelect
method.Regards, Nish
Website: www.voidnish.com Blog: voidnish.wordpress.com
-
I can't get my head round the Linq syntax, so I always use method chaining. I think it's the way the query is backwards (just like SQL) where method chaining fits C# syntax better (in my mind anyway).
Bad command or file name. Bad, bad command! Sit! Stay! Staaaay...
Same here, I never got the hang of it - never liked it to be honest, and always use C# method syntax.
Regards, Nish
Website: www.voidnish.com Blog: voidnish.wordpress.com
-
I'd be inclined to avoid joining the strings just to split them again. I'd also be inclined to use an array of
char
, rather than searching a string - although I doubt it would make much difference. You've also added aDistinct
and aToList
which weren't in the original example. :)char[] vowels = { 'a', 'e', 'i', 'o', 'u' };
IEnumerable<string> vowelStartWords = strings
.SelectMany(sentence => sentence.Split(' '))
.Where(word => Array.IndexOf(vowels, char.ToLower(word[0])) != -1)
;
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
I'm always delighted to have a chance to learn from your code ! I did add the call to 'Distinct with my first (and only) edit of the original post because I got to thinking that if you had a lot of 'if and 'else and 'and and 'is and 'or, etc., that would eliminate some duplicate calls. edit ... my head spins as I consider the ways you can use Linq to "coalesce collections:" ''SelectMany, 'Aggregate, 'Join, etc. thanks, Bill
«There is a spectrum, from "clearly desirable behaviour," to "possibly dodgy behavior that still makes some sense," to "clearly undesirable behavior." We try to make the latter into warnings or, better, errors. But stuff that is in the middle category you don’t want to restrict unless there is a clear way to work around it.» Eric Lippert, May 14, 2008
-
Learned something new today. I was googling for CRC algorithms and came across this nifty site[^] and started perusing it more generally, then realized I had no idea about let clauses in query expressions![^] Geez, I've been using LINQ for a while now, and didn't know about that. :doh: Marc
Imperative to Functional Programming Succinctly Contributors Wanted for Higher Order Programming Project! Learning to code with python is like learning to swim with those little arm floaties. It gives you undeserved confidence and will eventually drown you. - DangerBunny
Haha, I have to say I dunno "how to replace let with extension method" so when I want to introduce a variable it's one case where I definitely use linq query syntax over chaining LINQ extension methods....
All in one Menu-Ribbon Bar DirectX for WinRT/C# since 2013! Taking over the world since 1371!
-
I'd be inclined to avoid joining the strings just to split them again. I'd also be inclined to use an array of
char
, rather than searching a string - although I doubt it would make much difference. You've also added aDistinct
and aToList
which weren't in the original example. :)char[] vowels = { 'a', 'e', 'i', 'o', 'u' };
IEnumerable<string> vowelStartWords = strings
.SelectMany(sentence => sentence.Split(' '))
.Where(word => Array.IndexOf(vowels, char.ToLower(word[0])) != -1)
;
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
Console.WriteLine(
string.Join(
"\n",
strings.SelectMany(sentence => sentence.Split(' '))
.Where(word => Array.IndexOf(vowels, char.ToLower(word[0])) != -1)
.Select(vowelWord => $"\"{vowelWord}\" starts with a vowel")))
;;)
-
Console.WriteLine(
string.Join(
"\n",
strings.SelectMany(sentence => sentence.Split(' '))
.Where(word => Array.IndexOf(vowels, char.ToLower(word[0])) != -1)
.Select(vowelWord => $"\"{vowelWord}\" starts with a vowel")))
;;)
Shame there's no
foreach
extension method:public static class EnumerableExtensions
{
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (action == null) throw new ArgumentNullException(nameof(action));foreach (T item in source) { action(item); } }
}
Then you could do:
strings.SelectMany(sentence => sentence.Split(' '))
.Where(word => Array.IndexOf(vowels, char.ToLower(word[0])) != -1)
.Select(vowelWord => $"\"{vowelWord}\" starts with a vowel")
.ForEach(Console.WriteLine)
;:)
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
-
You're missing out on a lot! LINQ can be a bit slower, but it's awesome for many use cases. When you really need the milliseconds go for regular ADO.NET, but how often do you really need that? My experience with LINQ is not that it's slow to use, but that people suddenly forget that their LINQ expression becomes a SQL query and start writing the most horrible, non-indexed queries, now THAT is a performance killer. What I really like about LINQ is that you can create your own extension methods and use those to create queries that read like regular sentences, or just a lot better than SQL in general (I mean, who remembers why all those WHERE clauses are there?) :)
Read my (free) ebook Object-Oriented Programming in C# Succinctly. Visit my blog at Sander's bits - Writing the code you need. Or read my articles here on CodeProject.
Simplicity is prerequisite for reliability. — Edsger W. Dijkstra
Regards, Sander
I think you're mistaking Linq with Linq to SQL. ;-) Depending on the use case you might even gain performance, as an Enumerable is only evaluated when it's needed. As long as you're not accessing the items (or converting it into something other than an IEnumberable with "ToList" or "ToArray") the query isn't evaluated.
-
Shame there's no
foreach
extension method:public static class EnumerableExtensions
{
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (action == null) throw new ArgumentNullException(nameof(action));foreach (T item in source) { action(item); } }
}
Then you could do:
strings.SelectMany(sentence => sentence.Split(' '))
.Where(word => Array.IndexOf(vowels, char.ToLower(word[0])) != -1)
.Select(vowelWord => $"\"{vowelWord}\" starts with a vowel")
.ForEach(Console.WriteLine)
;:)
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
Well, you could convert the
IEnumerable
to a List withToList
and call theForEach
method of theList<T>
class. Should pretty much equal the foreach loop (as you need to evaluate the query anyways when looping) -
I think you're mistaking Linq with Linq to SQL. ;-) Depending on the use case you might even gain performance, as an Enumerable is only evaluated when it's needed. As long as you're not accessing the items (or converting it into something other than an IEnumberable with "ToList" or "ToArray") the query isn't evaluated.
Nicholas Marty wrote:
I think you're mistaking Linq with Linq to SQL. ;-)
I know the difference very well. I was just assuming we were talking about LINQ to SQL/Entities/whatever data source, because if we're talking LINQ to Objects there really isn't any performance issue to talk about :)
Read my (free) ebook Object-Oriented Programming in C# Succinctly. Visit my blog at Sander's bits - Writing the code you need. Or read my articles here on CodeProject.
Simplicity is prerequisite for reliability. — Edsger W. Dijkstra
Regards, Sander
-
Pete O'Hanlon wrote:
So how have you been solving this in the past? Multiple queries chained together?
My Linq tends to be rather simple. :)
Pete O'Hanlon wrote:
Though for reporting, yup, been there, done that:
var categoryRanks = (from gs in geekSkills
where (gs.ProfileId == profile.Id)
join s in skills on gs.SkillId equals s.Id
select new { Level = gs.Level, CategoryId = s.CategoryId } into gss
join c in categories on gss.CategoryId equals c.Id
select new { Level = gss.Level, Name = c.Name } into gssc
group gssc by new { gssc.Name, gssc.Level } into g
select new SkillLevelBySkillByCategory() {
SkillLevel = g.Key.Level,
SkillLevelCount = g.Count(x => x.Level == g.Key.Level),
Name = g.Key.Name });Marc
Imperative to Functional Programming Succinctly Contributors Wanted for Higher Order Programming Project! Learning to code with python is like learning to swim with those little arm floaties. It gives you undeserved confidence and will eventually drown you. - DangerBunny
Now, if your table have appropriately assigned foreign keys, then your Profile object would have a "geekSkills" collection of Skills object, which in turn would have a Category property which would have a Name property. Reducing that to :
var categoryRanks = from s in profile.geekSkills group s by new {Level = s.Level, Name = s.Category.Name} into g select new SkillLevelBySkillByCategory() { SkillLevel = g.Key.Level, SkillLevelCount = g.Count(x => x.Level == g.Key.Level), Name = g.Key.Name };
Not having the tables (or schema) makes designing that a bit tricky, so that might be off a bit. (like, i"m pretty sure that SkillLevelCount can be just "g.Count()" but I don't know your data) Now, using LET, we can bring that down to :
var categoryRanks = from s in profile.geekSkills
let sl = new {Level = s.Level, Name = s.Category.Name}
group sl by sl into g
select new SkillLevelBySkillByCategory() {
SkillLevel = g.Key.Level,
SkillLevelCount = g.Count(x => x.Level == g.Key.Level),
Name = g.Key.Name };Truth, James
-
You're not missing anything. I trialled Linq functions extensively a couple of years back and they were always slower (sometimes markedly so) than the traditional methods they 'replace'. It all looks very fancy and sophisticated but it's totally inefficient.
I am not a number. I am a ... no, wait!
Which "traditional" methods? ADO with DataSets? From my experience, LINQ is trivially slower (but with added type-safety benefits to offset it). BUT, linq makes it very easy to write bad queries. Things like, reading an entire table into an array, and then linearly search through it. SO, it you compare a carefully tuned, DBA written stored procedure against a simple query written by a developer with little experience with databases, well, then, LINQ is going to lose. But, it you compare two well-crafted queries, one in LINQ and one in SQL, then you should be nearly the same (since the LINQ will generate the exact same SQL).
Truth, James
-
Nicholas Marty wrote:
I think you're mistaking Linq with Linq to SQL. ;-)
I know the difference very well. I was just assuming we were talking about LINQ to SQL/Entities/whatever data source, because if we're talking LINQ to Objects there really isn't any performance issue to talk about :)
Read my (free) ebook Object-Oriented Programming in C# Succinctly. Visit my blog at Sander's bits - Writing the code you need. Or read my articles here on CodeProject.
Simplicity is prerequisite for reliability. — Edsger W. Dijkstra
Regards, Sander
Depending on what you're doing with Linq, it can be even faster than a loop – but it can also be slower by more than 100%. If the difference matters is really up to the specific case. I'd guess it doesn't really have a noticeable impact in most cases, as a few milliseconds are negligible.
-
Learned something new today. I was googling for CRC algorithms and came across this nifty site[^] and started perusing it more generally, then realized I had no idea about let clauses in query expressions![^] Geez, I've been using LINQ for a while now, and didn't know about that. :doh: Marc
Imperative to Functional Programming Succinctly Contributors Wanted for Higher Order Programming Project! Learning to code with python is like learning to swim with those little arm floaties. It gives you undeserved confidence and will eventually drown you. - DangerBunny
For a bit of background. LINQ facilitates functors and monads (concepts from functional programming). Simplified: functors are types that 'map' (Select), monads are types that 'bind' (SelectMany). When you do 'from x in y' you're lifting the value out of the monad (get the value wrapped up *in* the monad), doing an operation on it, and then wrapping it back up in the monad. The monad in this case being IEnumerable/IQueryable. There are other types, like Option, Either, Try - see this library: [language-ext] 'let' is essentially the non-monadic assign, it's essentially exactly the same as declaring a local variable. It doesn't do the lifting, and therefore if you did 'let x = list', then it wouldn't extract the value from the list, it will just assign the list. It's most useful in query expressions to pre-calculate a value that will be used many times in subsequent parts of the query; but it can very much allow full functional programming within LINQ expressions.
-
Shame there's no
foreach
extension method:public static class EnumerableExtensions
{
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (action == null) throw new ArgumentNullException(nameof(action));foreach (T item in source) { action(item); } }
}
Then you could do:
strings.SelectMany(sentence => sentence.Split(' '))
.Where(word => Array.IndexOf(vowels, char.ToLower(word[0])) != -1)
.Select(vowelWord => $"\"{vowelWord}\" starts with a vowel")
.ForEach(Console.WriteLine)
;:)
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
I actually thought about a nasty Linq-with-side-effect version with something like
.Select(vowelWord => Console.WriteLine($"\"{vowelWord}\" starts with a vowel"))
but of course you can't, because Console.WriteLine() returns Void. No doubt it would be possible to force something like this, but that'd be silly; it's actually quite neat that C# protects us from such folly. For one thing, it won't evaluate until the IEnumerable is enumerated, so you don't really know when that is just from that line of code.
-
Well, you could convert the
IEnumerable
to a List withToList
and call theForEach
method of theList<T>
class. Should pretty much equal the foreach loop (as you need to evaluate the query anyways when looping)Nice.
-
Now, if your table have appropriately assigned foreign keys, then your Profile object would have a "geekSkills" collection of Skills object, which in turn would have a Category property which would have a Name property. Reducing that to :
var categoryRanks = from s in profile.geekSkills group s by new {Level = s.Level, Name = s.Category.Name} into g select new SkillLevelBySkillByCategory() { SkillLevel = g.Key.Level, SkillLevelCount = g.Count(x => x.Level == g.Key.Level), Name = g.Key.Name };
Not having the tables (or schema) makes designing that a bit tricky, so that might be off a bit. (like, i"m pretty sure that SkillLevelCount can be just "g.Count()" but I don't know your data) Now, using LET, we can bring that down to :
var categoryRanks = from s in profile.geekSkills
let sl = new {Level = s.Level, Name = s.Category.Name}
group sl by sl into g
select new SkillLevelBySkillByCategory() {
SkillLevel = g.Key.Level,
SkillLevelCount = g.Count(x => x.Level == g.Key.Level),
Name = g.Key.Name };Truth, James
James Curran wrote:
Now, if your table have appropriately assigned foreign keys,
They do, but I haven't set up my models yet with EntityRef and EntitySet. I'll have to do that and then play around with what you're suggesting. I'll be really curious to log what the DataContext does! And thank you for the reductions, that was way beyond the call of duty! Marc
Imperative to Functional Programming Succinctly Contributors Wanted for Higher Order Programming Project! Learning to code with python is like learning to swim with those little arm floaties. It gives you undeserved confidence and will eventually drown you. - DangerBunny
-
James Curran wrote:
Now, if your table have appropriately assigned foreign keys,
They do, but I haven't set up my models yet with EntityRef and EntitySet. I'll have to do that and then play around with what you're suggesting. I'll be really curious to log what the DataContext does! And thank you for the reductions, that was way beyond the call of duty! Marc
Imperative to Functional Programming Succinctly Contributors Wanted for Higher Order Programming Project! Learning to code with python is like learning to swim with those little arm floaties. It gives you undeserved confidence and will eventually drown you. - DangerBunny
Marc Clifton wrote:
They do, but I haven't set up my models yet with EntityRef and EntitySet.
That shouldn't be necessary. As long as the tables have the foreign keys defined, both Linq2sql & Entity Framework should create the properties automatically.
Truth, James