Press "Enter" to skip to content

Lesson 19.3 – Clean up the source code by converting foreach to LINQ

Lesson objectives

At the end of this lesson, you will know…

  • How to make your program easier to read by using LINQ when working with lists

 

We still have several places in the game that can use improvement.

One way to make you code smaller, cleaner, and easier to understand is to replace some of the “foreach” loops (that usually are at least six lines long) with a LINQ statement (which is often one line long).

LINQ is short for Language-Integrated Query.

There are several different ways you can work with this, including one that looks similar to SQL (Structured Query Language – the language you use when you work with many databases). But we’re going to use one of the other methods that I like – lambdas. To me, lambdas make the code very easy to read.

 

How to replace a “foreach” loop with a LINQ statement

Step 1: Open the SuperAdventure solution in Visual Studio and open the Player.cs class.

 

Step 2: Find the HasRequiredItemToEnterThisLocation() method (it should be on line 30, if you haven’t changed anything). It should look like this:

public bool HasRequiredItemToEnterThisLocation(Location location)
{
    if(location.ItemRequiredToEnter == null)
    {
        // There is no required item for this location, so return "true"
        return true;
    }
    // See if the player has the required item in their inventory
    foreach(InventoryItem ii in Inventory)
    {
        if(ii.Details.ID == location.ItemRequiredToEnter.ID)
        {
            // We found the required item, so return "true"
            return true;
        }
    }
    // We didn't find the required item in their inventory, so return "false"
    return false;
}

In the first few lines, if there isn’t anything required to enter the location, we return “true”, to allow the player move to the location.

For the rest of the method, if there is an item required for the player to move to the location, we have a “foreach” loop through the player’s inventory, looking for the required item. The “foreach” loop, and the “return false” (if the item isn’t found), take twelve lines of code.

Let’s make it a little simpler.

 

Step 3: In order to use LINQ, we need to have it available in the class, with a “using” statement. In this case, we already have “using System.Linq;” at the top of the class, so we’re ready to make our change.

Our objective is to see if there is an item in the player’s inventory with an ID that matches the ID of the item required to enter the location. With the “foreach”, we do this by looping through each item, checking it’s ID.

With LINQ, we can reduce this to one line:

public bool HasRequiredItemToEnterThisLocation(Location location)
{
    if(location.ItemRequiredToEnter == null)
    {
        // There is no required item for this location, so return "true"
        return true;
    }
    // See if the player has the required item in their inventory
    return Inventory.Exists(ii => ii.Details.ID == location.ItemRequiredToEnter.ID);
}

Let’s compare the old method with the new one.

The Exists() function will check the items in the Inventory list, to see if any item matches the expression between the parentheses. If it finds an item, it returns “true”. If it doesn’t, it returns “false”.

Inside the parentheses, what we see is similar to what was between the parentheses in the “foreach” and the “if” statements in the old method.

To the left of the “=>” is “ii”. This is the variable name the LINQ expression will use for each item in the list – just like it did with the “foreach”.

To the right of the “=>” is the expression that is going to be evaluated. In this case, check if the inventory item’s ID matches the ID of the required item’s ID.

That’s how these lambda expressions work. The variable declaration for the list item is to the left of the “arrow”, and the expression is to the right.

 

Step 4: Now we’ll do the same thing to the HasThisQuest() method, and change it to this:

public bool HasThisQuest(Quest quest)
{
    return Quests.Exists(pq => pq.Details.ID == quest.ID);
}

 

Step 5: Find the HasAllQuestCompletionItems() method.

This one is a little more complex. We want to see if the player has the item required to complete a quest and if they have enough of those items in their inventory.

So, we’ll use this for our LINQ statement:

public bool HasAllQuestCompletionItems(Quest quest)
{
    // See if the player has all the items needed to complete the quest here
    foreach(QuestCompletionItem qci in quest.QuestCompletionItems)
    {
        // Check each item in the player's inventory, to see if they have it, and enough of it
        if(!Inventory.Exists(ii => ii.Details.ID == qci.Details.ID && ii.Quantity >= qci.Quantity))
        {
            return false;
        }
    }
    // If we got here, then the player must have all the required items, and enough of them, to complete the quest.
    return true;
}

This expression will see if the item exists in the player’s inventory (ii.Details.ID == qci.Details.ID) and if the quantity in the player’s inventory is greater than, or equal to, the quantity required to complete the quest (ii.Quantity >= qci.Quantity).

If the program doesn’t find an item in the list that matches both conditions, we’ll stop checking and return “false” for the method. If it gets through all the items required to complete the quest, the method returns “true” at the end.

We could go even further in cleaning up this method by writing a LINQ query for the remaining “foreach” in this method, but the query would be a little more complex than I want to show you right now.

 

Step 6: You can also get a specific item from a list with the SingleOrDefault method. However, you’ll need to check if it returned “null”, since nothing matched the condition. SingleOrDefault also only works if you’ll only ever have one item in the list that matches the condition. You’ll need to use a different LINQ method if you want to get more than one item from the list.

Here is how you can use SingleOrDefault in the RemoveQuestCompletionItem(), AddItemToInventory(), and MarkQuestCompleted() methods:

public void RemoveQuestCompletionItems(Quest quest)
{
    foreach(QuestCompletionItem qci in quest.QuestCompletionItems)
    {
        InventoryItem item = Inventory.SingleOrDefault(ii => ii.Details.ID == qci.Details.ID);
        if(item != null)
        {
            // Subtract the quantity from the player's inventory that was needed to complete the quest
            item.Quantity -= qci.Quantity;
        }
    }
}
public void AddItemToInventory(Item itemToAdd)
{
    InventoryItem item = Inventory.SingleOrDefault(ii => ii.Details.ID == itemToAdd.ID);
    if(item == null)
    {
        // They didn't have the item, so add it to their inventory, with a quantity of 1
        Inventory.Add(new InventoryItem(itemToAdd, 1));
    }
    else
    {
        // They have the item in their inventory, so increase the quantity by one
        item.Quantity++;
    }
}
public void MarkQuestCompleted(Quest quest)
{
    // Find the quest in the player's quest list
    PlayerQuest playerQuest = Quests.SingleOrDefault(pq => pq.Details.ID == quest.ID);
    if(playerQuest != null)
    {
        playerQuest.IsCompleted = true;
    }
}

In this situation, it doesn’t really reduce the amount of code. But you may find this useful in a future program.

 

Check your work

Build the solution and make sure there are no errors in the “output” box at the bottom of Visual Studio. If you see any problems, double-check the changes you made in this lesson.

 

Summary

This covers just one way to use LINQ. You can also do things such as calculating the sum of a property for items in a list:

int sum = Inventory.Sum(ii => ii.Quantity);

You can build a chain of LINQ statements, like this (which will give you the sum of the Quantity of all items in the Inventory list, for items that have a Quantity greater than five):

int sum = Inventory.Where(ii => ii.Quantity > 5).Sum(ii => ii.Quantity);

 

Source code for this lesson

Source code on GitHub

Source code on Dropbox

 

Next lesson: Lesson 19.4 – Saving and loading the player information

Previous lesson: Lesson 19.2 – Use a calculated value for a property

All lessons: Learn C# by Building a Simple RPG Index

6 Comments

  1. Julian
    Julian February 21, 2016

    Hi Scott,

    Thanks for the great tutorial!! It’s really really fun to be able to learn C# and make a complete (and fun) program along the way.

    I have a question though: in your new RemoveQuestCompletionItems method, you retained the break statement after subtracting the item quantity. If there was more than one QuestCompletionItem in the QuestCompletionItems list, wouldn’t this cause the foreach loop to stop after the first QuestCompletionItem, and not continue on to the other QuestCompletionItem(s)?

    Also, for the same method, is the null check necessary? We only call it after making sure that playerHasAllItemsToCompleteQuest == true, so there will always be an item. If OTOH we mistakenly call it when playerHasAllItemsToCompleteQuest == false, wouldn’t the absence of a null check throw an exception, and make it easier for us to discover and fix the problem (problem being the incorrect call)? If indeed the null check is necessary, then shouldn’t we also, for consistency, re-check that item.Quantity >= qci.Quantity so that we don’t accidentally make the quantity negative? Sorry for the long question, I’m trying to figure out when and when not to use a null check.

    Thanks for all the help!

    • Scott Lilly
      Scott Lilly February 21, 2016

      Hi Julian,

      You are correct. That “break;” statement needed to be removed. I remove it in Lesson 20.4, when we make some more changes. However, it should not be in that function. I fixed the posts and files for the next few lessons, where it had the “break;”. Thank you for catching that.

      The null check is not really needed here, because we only call this function after passing HasAllQuestCompletionItems. However, I have a habit of always doing a null check after using the SingleOrDefault LINQ function, to get an item from a list. At my job, I work on programs that do work asynchronously (not all parts are working at the same time). We always need to do this check, in case the object does not exist any more.

      If we were writing a more complex game, like World of Warcraft, we would definitely need to do the null check and include re-checking the quantity. Not re-checking everything could lead to exploits.

  2. Julian
    Julian February 22, 2016

    Thanks for the great explanation, Scott! Asynchronous programs sound very challenging. Indeed it does seem like all those checks would be very crucial for those type of programs.

    Thanks again!

  3. Tim
    Tim June 1, 2017

    For anyone else going through these lessons, I noticed a bug where a player could attack from their home after dying to a monster, here’s the fix:

    Under this if statement in SuperAdventure.cs
    if (_player.CurrentHitPoints <= 0)

    Add this code
    btnUseWeapon.Visible = false;
    btnUsePotion.Visible = false;
    cboWeapons.Visible = false;
    cboPotions.Visible = false;

    • Tim
      Tim June 1, 2017

      You could even do it on one line as follows:
      btnUseWeapon.Visible = btnUsePotion.Visible = cboWeapons.Visible = cboPotions.Visible = false;

    • Scott Lilly
      Scott Lilly June 1, 2017

      Thanks for sharing that. I think there is a change in a future lesson that makes a similar fix. But, that is a good change to add in.

Leave a Reply

Your email address will not be published. Required fields are marked *