In this article I’m going to talk about my personal journey of discovery using Linq library expressions. The examples are written and compiled in Unity, so some reference to that API may be mentioned. This isn’t an advanced look at Linq statements, more of a fool’s guide to what they are and how I’ve found them useful in my own code. This might be day-one info for comp/sci majors; if you already use Linq and lambda queries in your code, feel free to skip this one. In Part 1 we will cover lambda expressions and also how to make a basic Linq query.
What is Linq?
LINQ stands for “Language Integrated Query” – a library for .NET languages that has been directly integrated into C#. Essentially, it allows you to use SQL-style query language in your code, which can have a lot of applications. But this article is going to focus on using Linq in conjunction with lambda expressions to quickly reference objects in lists and arrays. Fun, right?
OK here’s an example – let’s say you have a list of spawnable enemy objects in your game. You’re using this list to keep track of every enemy, but sometimes you just want to reference the enemies that are “spawned” and are on-screen. Before applying our logic to each guy in this list, we need to check to see if they’re available, which means a for-loop and some conditional logic.
“Well wait,” you say. “Another option would be to keep two lists instead – a ‘spawned’ and ‘sleeping’ list of enemies.” While this approach works and might be convenient in certain cases, these days I am trying to avoid “sorting lists” as much as I can. There are two reasons:
- The upkeep of moving items between lists is usually about the same amount of code (or more) than searching through a master list.
- Keeping lists in sync becomes more trouble than it’s worth. Making sure one item is not in multiple lists, or that one item IS in multiple lists (depending on your structure), can be a pain. This is especially true when it comes time to add and delete items, and that can lead to extra code and annoying bugs.
This is worth mentioning because Linq statements are great for working with “master lists,” and by extension are a good tool for avoiding the sorting-list trap.
Why do we use sorting-lists to begin with? In many cases, we use them in a desperate attempt to write less for-loops. It’s true that Loops are an important part of programming, especially when it comes to working with lists and arrays. But damn. Sometimes you just need to find that one list object for JUST A SEC, right? An how painful is typing out that foreach statement you’re only going to use once? If you think about it, every bend of your finger is a small step on the path towards that eventual career-ending arthritis… And that’s where Linq may be able to help. No, not with the existential crisis part. A Linq expression lets you quickly and efficiently grab one or more items from a list, and does so in a more compact, space-efficient way. But there are some tricks to using them. Without further ado:
Lambda Expressions
These intimidated me for a long time. Lambda expressions are a key part to using Linq, and so it’s important that we get a handle on them first. They are described in the manual as “anonymous functions” – which I don’t find to be very helpful. I prefer to think of them as mini-functions; they take arguments and apply logic to them, and do it all in a single line.
In a lambda expression, the argument is on the left hand side, the logic applied to it is on the right, and they are separated by this symbol: =>
( x => x % 2 == 1)
- “x =>” tell us that whatever is calling this function is going to be passing some parameter, and well will store that as “x.”
- “x % 2 == 1” is the expression
- this lambda will return “true” if the passed number is odd (because an odd number modulo 2 will have a remainder of 1)
This statement is essentially the same as this:
public bool MyUnlovedFunction( int x ){ if( x % 2 == 1 ){ return true; } else{ return false; } }
But as you can see, much slimmer and sexier. What we lose when writing this as a lambda is a name for our function, hence, anonymous. But that isn’t important if we are only going to call this function during a specific search.
We use these kinds of lambdas in our Linq expressions to find specific items or groups of items in lists. If we used this expression on a list of integers, it would return a collection of every odd number in that list. But how do we do that? Stay tuned for….
Querying A List
To use Linq, first you must include the library with a using statement, like so:
using UnityEngine; using System.Collections; using System.Linq; // this guy
Now, let’s make an array of integers to work with. Linq can be used with Lists or Dictionaries as well.
int[] myNumbers = new int[5] {1, 2, 3, 4, 5};
Let’s say that, for whatever reason, I just want to grab numbers in this list that are greater than 2. Here’s what that might look like just using loops and lists:
List myUnnecessaryList = new List(); foreach( int num in myNumbers ){ if( num > 2 ){ myUnnecessaryList.Add( num ); } } foreach( int i in myUnnecessaryList ){ print( i ); }
And this is basically the same action performed using a Linq expression.
var myBigNumbers = myNumbers.Where( x => x > 2 );
Boom. I’ll give you a moment to recover, then we’ll break this down.
This statement uses a very common method of Linq, the “Where()” method. “Where()” can be called on arrays, lists or similar collections, and IEnumerables. As you can see, we pass a lambda expression as the argument to Where(). What Linq does is iterate through the collection, in this case myNumbers, and applies the lambda. The value of x becomes the current item in the collection we are checking, in this case an integer. If the integer is greater than two, then the lambda expression equates true, and that item is added to myBigNumbers.
There you have it, the basics of using Linq. This is a simple example – in Part 2 I’m going to show you a few more useful Linq tricks that will build on this idea, and let you flick through your lists like a magician with a deck of cards. I will also address a lot of the common pitfalls and issues I ran into while I was learning, so be sure to check back in a few days!