简体   繁体   中英

Order of operations in a foreach loop

I completely changed the way my crafting table works and I've run into a problem. The way it works now is it displays all the item in the crafting list. If you can't make the item, the item texture in the table is translucent.

My problem is that when I am checking if the item can be crafted, I am setting a bool variable CanCraft to true or false and it is not setting correctly.

 public void CheckForAvailableCrafts(Player player)
    {
        foreach (ItemRecipe recipe in CraftingList)
        {
            if (recipe.CheckForItemsNeeded(player) != null)
            {
                Output = recipe.Output;
                UpdateTable(Output);
            }
            else if (recipe.CheckForItemsNeeded(player) == null)
            {
                Output = new Item();
                Output.ItemName = "empty";
                UpdateTable(Output);
            }

The CheckForItemsNeeded method works correctly because it worked fine before I rewrote this part of code. If I have a certain amount of items for the recipe CanCraft for the item is true. The problem lies within the UpdateTable method.

            private void UpdateTable(Item output)
    {
        foreach (Item item in CraftingTableItems)
        {
            if (output.ItemName == item.ItemName)
            {
                item.CanCraft = true;
                if (output.ItemName == "empty")
                {
                    item.CanCraft = false;
                }
            }
        }
    }

My CraftingTableItems is a list of 12 items, and each item is set to a recipe in my CraftingList. So I have 6 items in the CraftingTable that aren't empty.

After I've crafted the item, the item in the table should return to a false value, but it doesn't. Is the order of operations in my for loop correct?

I suspect the problem is the Output variable. It's not defined as part of the method, and therefore could be modified by other code from another thread. You could change the code to make that variable local to the method (general rule of thumb: always use the smallest scope possible for a variable) or even eliminate it entirely, but I'd re-write the whole thing like this:

public void CheckForAvailableCrafts(Player player)
{
    //names of items the player can craft
    var names = CraftingList.Where(r => r.CheckForItemsNeeded(player) != null)
                            .Select(r => r.Output.ItemName);

    //Item objects that player can craft
    var items = CraftingTableItems.Join(names, i => i.ItemName, n => n, (i, n) => i);

    //Set all to false (base state)
    foreach (var item in CraftingTableItems) {item.CanCraft = false;}
    //Set items player can craft back to true
    foreach(var item in items) {item.CanCraft = true;}
}

Not only is that less code, but it should be much more efficient, as the prior code had to check every CraftingList entry for every possible item the player could craft... O(n 2 ), while this is (I think) O(n Log n), maybe even close to O(n).

Yes, that .Join() call looks like greek if you're not used to lambda expressions, but it's really not that hard, and again: it greatly simplifies and speeds your code. You can simplify this further if you have an EqualityComparer class the compares two items based on the name, or if your Item type implements IEquatable to compare on ItemNames (and you may find that useful anyway). Assuming the latter, where Item correctly implements IEquatable, your code would look like this:

public void CheckForAvailableCrafts(Player player)
{
    //items the player can craft
    var craftableItems = CraftingList.Where(r => r.CheckForItemsNeeded(player) != null)
                                     .Select(r => r.Output);

    //Items objects from the CraftingTable that the player can craft
    craftableItems = CraftingTableItems.Intersect(craftableItems);

    //Set all to false (base state)
    foreach (var item in CraftingTableItems) {item.CanCraft = false;}
    //Set items player can craft back to true
    foreach(var item in craftableItems) {item.CanCraft = true;}
}

If you can't spot the difference at first, the first statement saves a step in the Select projection, and the second statement changes from the ugly Join() function to the easier-to-read Intersect()

这里是在黑暗中拍摄,但如果有人命名略有不同,我将做以下检查:

output.ItemName.ToLower() == item.ItemName.ToLower()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM