LINQ lets you manipulate collections super-easily, eliminating the need for loops and temporary variables in many cases.
here's a simple example: finding even numbers from a list of numbers, using a loop:
public List<int> FindEvenNumbers(List<int> numbers)
{
var evenNumbers = new List<int>();
foreach(var n in numbers)
{
if(n % 2 == 0)
evenNumbers.Add(n);
}
return evenNumbers;
}
these kinds of loops are really common, but usually with more interesting conditions...
public Tile FindBestMonsterMove(Monster monster)
{
var allTiles = GetAllTiles();
var availableTiles = new List<Tile>();
foreach(var t in allTiles)
{
if(TileMath.Distance(monster.Tile, t) <= monster.MovementSpeed && t.CanBeMovedInto())
availableTiles.Add(t);
}
// TODO: decide which availableTile is the best to move the monster into
// (probably more loops...)
return bestMove;
}
but let's go back to finding even numbers for now.
foreach(var n in numbers)
{
if(n % 2 == 0)
evenNumbers.Add(n);
}
any time you're transforming one list into another - be it filtering, duplicating, changing from one type to another, etc - LINQ can probably help you do it way-easier!
"LINQ" is just the name of a library that contains a bunch of methods that operate on collections (arrays, List
s, IEnumerable
s, etc) to let you turn one collection into another (or a collection into a single element; more on that later). you tell these methods what logic they should use (by giving it a function - called a "delegate" in this context), and it applies that logic to every element.
(LINQ comes with C#. add using System.Linq;
to your C# files, if your IDE didn't already include it for you.)
for example: in order to take a list of numbers, and get just the even numbers, you apply some evaluation to every number - n % 2 == 0
- and if a number passes that evaluation, it's kept!
the LINQ method that does this "keep it or leave it" evaluation is called Where
. you're finding the elements in some list where some condition is met. (in JavaScript they call this method filter
, which I feel is a little more descriptive, but whatever. Where
is fine, too.)
public List<int> FindEvenNumbers(List<int> numbers)
{
return numbers.Where(n => n % 2 == 0).ToList();
}
that's approximately WAY shorter than the loop-based FindEvenNumbers
method!
I can imagine two questions to the above code: "what's up with the funny n => n % 2 == 0
", and "what's up with the .ToList()
at the end?"
first: what's up with the funny n => n % 2 == 0
.
that's a clever short-hand way of writing functions in C#, as an ANONYMOUS LAMBDA FUNCTION (more on these exciting words in a moment). by comparison, a more-verbose way would have been:
public List<int> FindEvenNumbers(List<int> numbers)
{
return numbers.Where(IsEven).ToList();
}
private bool IsEven(int n)
{
return n % 2 == 0;
}
Where
needs to know the logic to apply to every element, so it needs a function which takes in a value of the type in the list (in this case, int
), and returns a bool that tells it "keep it (true), or don't (false)".
you CAN write out the whole dang function, and then give the name of that function to Where
, like the example immediately above, but the developers of C# were like "hey, sometimes you just want to define a quick little function that you don't really care about re-using, or even naming! what if we made a way to do that?" so they did!
here's some vocabulary that will make super-duper computer science architecture algorithm people (i.e. NERRRRDS) happy:
- delegate: a function that you're probably not going to call yourself, but instead pass to some other function, for it to invoke on your behalf
- anonymous function: a function without a name
- lambda expression: an anonymous function where you use the slick
=>
syntax to describe the function's parameters and return value
second question: what's up with the .ToList()
at the end?
return numbers.Where(n => n % 2 == 0).ToList();
Where
(and other LINQ methods) are sneakily efficient by not actually allocating a List
until you need one. if you leave off the .ToList()
at the end, then the above code would instead return an IEnumerable<int>
! IEnumerable
s are lazily evaluated when you loop over them. if that doesn't make a ton of sense, don't worry about it too much, but the point is: you can string together .Where
, and other LINQ methods, one after another, and you don't have to be afraid that each one is making a whole new list, because they aren't! only when you call .ToList()
is a list finally created!
- there's also
.ToArray()
, if you prefer arrays for some reason - in some cases, using an
IEnumerable
is fine - no need to ever allocate a list or array! you can worry about it later, when/if you start dealing with HUGE collections; for now, just tuck this away in some corner of your mind-brain: if you can get away without calling.ToList()
or.ToArray()
, and just pass aroundIEnumerable
s instead, that's probably pretty cool
so what are some other LINQ methods?
well, here's another problem, solved in a traditional, loop-y way, which - spoilers - there's a LINQ method for:
public List<int> SquareNumbers(List<int> numbers)
{
var squareNumbers = new List<int>();
foreach(var n in numbers)
squareNumbers.Add(n * n);
return squareNumbers;
}
we're taking one list, and generating a new one by performing some operation on every element. in this case, we're squaring.
the LINQ method that does this is Select
. (and since I mentioned JavaScript before, in JavaScript this method is called map
. I don't like either of these names.)
public List<int> SquareNumbers(List<int> numbers)
{
return numbers.Select(n => n * n).ToList();
}
by the way, since we're using that =>
syntax, now, mind if I BLOW YOUR MIND?
public List<int> SquareNumbers(List<int> numbers) => numbers.Select(n => n * n).ToList();
yeah, you can do that. but don't get too carried away: the quest for one-liners often leads to madness.
anyway: in that example, we turned int
s into int
s, but you can turn things into different kinds of things, if you want!
let's turn some numbers into some strings!
public List<string> NumberOfBeersOnTheWall(List<int> numbers)
{
return numbers.Select(n => n + " bottles of beer on the wall, " + n + " bottles of beer. Take one down, pass it around, " + (n - 1) + " bottles of beer on the wall.").ToList();
}
not to get off topic, but we can use string interpolation to make things a little tidier. also, the line is getting kinda' long, so I'm gonna split it up a little:
public List<string> NumberOfBeersOnTheWall(List<int> numbers)
{
return numbers
.Select(n =>
$"{n} bottles of beer on the wall, {n} bottles of beer. " +
$"Take one down, pass it around, {n - 1} bottles of beer on the wall."
)
.ToList()
;
}
(pro tip: when the LINQ statements start getting big, split them across multiple lines! this was kind of a wonky example, with the super-long string, but you get it; you're smart.)
and don't be shy: you can use .Select
to do some pretty large transformations:
// a class that stores a number, and some facts about that number:
class NumberFact
{
public int Number;
public string HexidecimalRepresentation;
public bool IsEven;
public int Square;
public bool IsMyFavorite;
}
...
// takes a list of numbers, and returns a list of number facts about those numbers!
public List<NumberFact> GetNumberFacts(List<int> numbers)
{
return numbers
.Select(n => new NumberFact() {
Number = n,
HexidecimalRepresentation = n.ToNumber("X"), // some ToString magic bullshit; don't worry about it
IsEven = n % 2 == 0,
Square = n * n,
IsMyFavorite = n == 7
})
.ToList()
;
}
there are several other LINQ methods for manipulating lists, like .OrderBy
, .OrderByDescending
, and the mind-bending .SelectMany
!
also, so far we've been turning lists into lists, but a few LINQ methods pick a single item out of a list - like .First
and .FirstOrDefault
- or reduce a list down to a single item - like Aggregate
.
here's an easy example:
public Person GetOldestPerson(List<Person> people)
{
return people
.OrderBy(p => p.Birthday) // order by the "Birthday" property on each person
.FirstOrDefault() // return the first one (or null - the "default" value of type Person - if the set is empty)
;
}
stop for a moment to think about how you'd write something like this with loops...
public Person GetOldestPerson(List<Person> people)
{
Person oldestPerson = null;
foreach(var p in people)
{
if(oldestPerson == null || oldestPerson.Birthday < p.Birthday)
oldestPerson = p;
}
return oldestPerson;
}
gross.
and, sorry, but I just can't help myself:
public Person GetOldestPerson(List<Person> people) => people.OrderBy(p => p.Birthday).First();
again: the quest for one-liners often leads to madness, but this demonstrates just how much we've been able to simplify our method. it's not even CONCEIVABLE that the loop-based implementation would fit on one line. the LINQ-based solution CAN, even if it probably SHOULDN'T!
but the REAL power/fun comes in chaining LINQ statements together, one after another. a more-exciting example is coming up, but for now, what if we wanted to square only the even numbers?
public List<int> SquaresOfEvenNumbers(List<int> numbers) => numbers
.Where(n => n % 2 == 0) // only even numbers, plz!
.Select(n => n * n) // square 'em!
.ToList()
;
(pro tip: one-liners don't have to be on one line! even when each LINQ statement is short, putting each on its own line greatly increases readability, even without silly comments! pro-pro tip: you don't need to include silly comments!)
when you're comfy with LINQ, you can accomplish a lot with very few lines of code:
public Tile FindBestMonsterMove(Monster monster) => GetAllTiles()
.Where(t =>
TileMath.Distance(monster.Tile, t) <= monster.MovementSpeed &&
t.CanBeMovedInto() // breaking long && statements across multiple lines helps, too!
)
.Select(t => new TileConsideration() {
Tile = t,
Weight = EvaluateHowGoodItIsToMoveToThisTile(monster, t)
})
.OrderByDescending(t => t.Weight) // sort by "Weight" property, from largest to smallest
.FirstOrDefault() // pick the first element, or null if the list is empty
;
of course, there's a lot of logic hidden in that EvaluateHowGoodItIsToMoveToThisTile
method... spoilers: it probably contains more LINQ :P
LINQ is great!