I’ve solved a C# beginner Kata on Codewars, asking to return a string[] with only 4-character names. I used a list that is filled in an if statement and then converted back to a string and returned.
My question is regarding the best practice solution below, which is presented later.
I understand that the same string[] that comes as an argument is refilled with elements and returned. But how does the program know that each element of the array is called “name”, as it’s never mentioned before?
Does linq know that a variable with a singular name is an element of a plural name group, here “names”?
Thanks for helping out!
using System;
using System.Collections.Generic;
using System.Linq;
public static class Kata {
public static IEnumerable<string> FriendOrFoe (string[] names) {
return names.Where(name => name.Length == 4);
}
}
CodePudding user response:
I understand that the same string[] that comes as an argument is refilled with elements and returned.
No, absolutely not. A completely new array is returned based on the input array. The input array is not modified or changed in any way.
But how does the program know that each element of the array is called “name”, as it’s never mentioned before?
name
is a parameter to an anonymous function. The name
parameter is a string based on context. This could be x
or ASDASDASD
or whatever you want, but here we use name
since we have, on each call, one "name" from names
.
Thus,
names
is an array of strings passed into the function- the
.Where
returns a new array from the current array based on a predicate function (e.g. returns true for a match, false to omit) - The predicate
name => name.Length == 4
takes a string and returns true if the string is length 4 - The return from the function is the strings from
names
that are exactly 4 characters in length
CodePudding user response:
I understand that the same string[] that comes as an argument is refilled with elements and returned
I'll address this briefly, because it's not the question you're asking. This isn't true - no refilling of anything occurs. The original array is unaltered and a Where is a loop that runs over the array and selectively emits items from it when the test presented to it evaluates to true for that item. The way it does this is via a special construct called a yield return
which is a way of allowing code to return from a method and then re-enter it and carry on from where it left off before, rather than starting all over again from the beginning of the method. There is only ever one array, and the looping/testing is not performed unless you start reading from the set of strings produced by the Where. If you want to know more about that, drop a comment.
Moving on..
Does linq know that a variable with a singular name is an element of a plural name group, here “names”?
No; the IDE knows the variable name
because that's what you chose to call it just after the Where(
Perhaps it would help to link it to something you already know. It would be perfectly acceptable to write this code:
public static class Kata {
public static IEnumerable<string> FriendOrFoe (string[] names) {
return names.Where(IsNameOfLengthFour);
}
static bool IsNameOfLengthFour(string name){
return name.Length == 4;
}
}
Where demands some method be supplied that takes a string and returns a boolean. It demands that the input be a string because it's being called on names
which is an array of string. If it were ints in the array, the method passed to Where would have to take an int
In C# there's often a push to make things more compact, so to get rid of all that wordiness above, we have a much more compact form of writing method bodies. Let's reduce our wordy version:
static bool IsNameOfLengthFour(string name){
return name.Length == 4;
}
We can chuck out the return type, because we can guess that from the type being returned. We can get rid of static too, and just assume static if we're calling it from inside another static, or not if we're not. We can ditch the input type too, because we can guess that from the type going in.
IsNameOfLengthFour(name){
return name.Length == 4;
}
If we have a special syntx that is only one line and must be a value that is auto returned, we can get rid of the return
, and the {}
because it's only one line, so we don't need to fence off multiple statements:
IsNameOfLengthFour(name) => name.Length == 4
Now, we don't actually need a method name any more either if we're going to use this in some place where a name is irrelevant, and we really don't need ()
for a single argument either:
name => name.Length == 4
And that's enough of an expression for the compiler to be able to form a method out of it, and plumb it into something that expects a method taking a string and returning a boolean. We've thrown away all the fluff of a method that we humans like (names and identifiers) and given the compiler just the raw nuts and bolts it needs - the logic of the method. The compiler will recreate the rest of the fluff when it wires it all together for us; we won't ever be able to call this mini-method from elsewhere in our code but we don't care. We got what we wanted, which is a nice compact way of expressing the logic:
Where(n => n.Length==4);
You did a good job, calling the argument to this mini-method something sensible. I see x
used a lot and it gets really confusing when what X is changes.. For example:
names
.Where(name => ...)
.GroupBy(name => ...)
.Select(g => g.First())
.Where(name => ...)
Where
works on your array of names so calling the argument to the delegate name
or n
is a good idea. Where will filter it down but ultimately it still emits a set of strings that are names, so it's still a good idea to call it name
on the way into a GroupBy.. But a GroupBy produces a set of IGrouping, not a set of string so the thing coming our of a GroupBy is no longer a name.. In the next Select I call it g
to reflect that it's a grouping, not a name, but I then take the first item in the grouping which is, actually, a name.. So in the final Where I go back to calling the input argument name
to reflect what it's back to being..
When LINQ statements get much more complicated, it really helps to name these arguments well
Note: in this answer I've used words like "list" or "set" and I mean those in the general English sense that an "array is a list of ..", not a specifically C# List<xxx>
or HashSet<xxx>
sense. If you see lowercase words that align with C# types, they are not intended to refer to that specific type