Is there a better and cleaner way then foreach? C#

Hey,
So, this is what i’m trying to reach: That if you press “V” in the game it will get all script of type “Outline” in a certain range and turn them on and wen i release the key it wil turn al those scripts back off!

I thought of using a array and find then al those script and put them in an array, but then i need to use foreach and that is something that is working slowly and not clean. Is there a better way of getting this done that wil work just fine and much cleaner?!

All help is Appreciated!

Why are you of the opinion a loop is not clean?

Share your code, and we can see what might be dirty… but I don’t suspect it’s the loop.

2 Likes

You just don’t want to iterate over things you don’t want to mess with, I understand that. I assume you mean in a “range” as in you have them in an array 0-100 and you know you want to hit 25-50 or w/e.

I think somehow you got exposed to foreach before for() which is ridiculous because foreach is actually a complex beast hiding behind there involving collections- anything you make IEnumerable can be iterated by foreach. There are some things foreach can’t do.

I strongly recommend not opening the foreach / IEnumerator / IEnumerable can of worms until you’re good and ready. I thought I was and it still took like four days to get down.

So you just want something like:

int amount = 10
int start = 10

for(int i = 0; i < amount; i++)
{
    someGOArray[ start + i ].SetActive(true);
}

// or maybe work backwards

for(amount; amount > 0; amount--)
{
    someGOArray[ start + amount ].SetActive(true);
}

Don’t think of loops as a bad thing, almost everything a computer does is in some way a loop, and yeah, if you’re setting 10 things you’re doing 10 things no matter what anyway. The only other possible option would be to store 10 different references to those objects and put 10 individual SetActive(true) calls.

Now what you gotta realize is, the computer is just translating your for loop into exactly that.

1 Like

If I’m not mistaken doesn’t foreach generate about 24 bytes of garbage? He may be referring to that.

It has to generate and use an IEnumerator, so yes it would create garbage a regular for loop does not. It should be trivial unless you’re doing it a lot though.

That whole mess is a nuanced structural design thing. If you combine it with an IEnumerable method and a yield return correctly you can save some memory.

1 Like

The foreach garbage problem should be fixed in Unity 5.5

1 Like

Yes, unless you do it with an array (which OP talks about), in which case the compiler optimizes it.

Thing is the way the OP phrased it, it didn’t sound like they were thinking this nuanced.

3 Likes

Thanks. I thought I remembered them mentioning it fixed in a recent release. Knowing the number made it much easier to narrow down where a comment about it was made by one of the developers.

https://blogs.unity3d.com/2016/08/30/get-the-unity-5-5-beta-now/

1 Like

I don’t know how you can “fix” an IEnumerator being garbage when it’s done being used, lol. Because it is garbage at that point.

I would guess by using the stack instead of the heap?

Unity and garbage collection: Unity - Manual: Memory in Unity

I know exactly what GC is I just don’t think any of you actually know what an IEnumerator is which is why you think there’s something wrong with foreach and I think it’s probably working as intended.

Well actually, since most enumerators in the built in collections are ‘structs’, if you access them directly you can avoid allocating an object on the heap to later be garbage collected. Instead having a temporary struct on the stack… that is unless you force it on the heap.

var lst = new List<object>();
//fill lst
List<object>.Enumerator e = lst.GetEnumerator();
while(e.MoveNext())
{
    var obj = e.Current;
}

As you can see ListEnumerator is a struct:

Using this technique of the ‘while(e.MoveNext())’ is very common in the unity community due to the performance hit GC calls take on games (the old mono runtime unity uses has a very naive non-generational garbage collection implementation). Despite if many people who do it, don’t know WHY they’re actually doing it.

So by using the ‘foreach’ you are forcing the enumerator onto the heap. This is because the struct implements IEnumerator, and the foreach treats it as the interface type rather than directly as the concrete type. This is because the underlying IL doesn’t actually have the concept of a ‘foreach’ loop and it actually gets unraveled as the while(e.MoveNext()), but the ‘e’ is typed IEnumerator instead. The issue being that when you cast a struct as its interface, it gets boxed, and placed on the heap.

This boxing is where the garbage is coming from.

Hence why we use that same boilerplate to avoid the gc.

Now there’s a way this could be fixed, and that’d be if the compiler didn’t coerce the struct Enumerator into an IEnumerator, but instead typed it as its concrete type. I’m not sure if this is actually fixed in Unity 5.5, haven’t looked to confirm (don’t have anything to test that with on hand right this moment). But it is a possibility.

I hope to check when I get back to my house and can install Unity 5.5 onto a virtual and take a look at the IL generated.

Note though, if it were fixed… it’d only count if your source collection implements it Enumerator as a struct, AND collection is referenced as its concrete type as well. If you use a List, but store it in a variable of type IList, the boxing will still occur as the compiler wouldn’t know which type it is specifically and would have to rely on the generic IList interface which only returns IEnumerator. The complexity of sorting out all this based on the type in question is why the compiler just coerces to IEnumerator in all cases (accept array), because it’s just faster/easier. And the old compiler used by Unity (pre 5.5) is rather… proof-of-concept… from the early days of mono. So they cut a lot of corners (mono community, not unity specifically).

3 Likes

What I don’t understand is that why we’re even still on about this considering that OP hasn’t graced any of us with a response yet.

1 Like

Well, maybe the op is not interested in this but I am! :slight_smile:

The piece of code you posted seems pretty much advanced level to me, could you provide a complete working script with a list and MoveNext () as an example? I’m still a beginner at coding and I need sketches. :wink:

Better: in this script below that I found here:

how would you replace foreach by MoveNext ().

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class SomeClass : MonoBehaviour
{
    void Start ()
    {
        //This is how you create a list. Notice how the type
        //is specified in the angle brackets (< >).
        List<BadGuy> badguys = new List<BadGuy>();
       
        //Here you add 3 BadGuys to the List
        badguys.Add( new BadGuy("Harvey", 50));
        badguys.Add( new BadGuy("Magneto", 100));
        badguys.Add( new BadGuy("Pip", 5));
       
        badguys.Sort();
       
        foreach(BadGuy guy in badguys)
        {
            print (guy.name + " " + guy.power);
        }
       
        //This clears out the list so that it is
        //empty.
        badguys.Clear();
    }
}

Thanks! :slight_smile:

Fairly simple swap out:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class SomeClass : MonoBehaviour
{
    void Start ()
    {
        //This is how you create a list. Notice how the type
        //is specified in the angle brackets (< >).
        List<BadGuy> badguys = new List<BadGuy>();

        //Here you add 3 BadGuys to the List
        badguys.Add( new BadGuy("Harvey", 50));
        badguys.Add( new BadGuy("Magneto", 100));
        badguys.Add( new BadGuy("Pip", 5));

        badguys.Sort();
       
        var e = badguys.GetEnumerator();
        while(e.MoveNext())
        {
            BadGuy guy = e.Current;
            print (guy.name + " " + guy.power);
        }

        //This clears out the list so that it is
        //empty.
        badguys.Clear();
    }
}

Note though, since that List is so short lived, it’ll generate garbage.

But yeah, that’s the basic boilerplate for ya.

1 Like

Quick test foreach over a List:
5.4.2f1: GC Alloc = 40 B
5.5.0f3: GC Alloc = 0B

So it’s fixed. No need to write the worst code on the reg to avoid an alloc from a simple loop.

6 Likes

How do you do that kind of tests?

1 Like

Profiler

It’s built into Unity

It will allow you to dig into scripts, through the stack, and measure where garbage is being generated, and how much. As well as much more other information. It used to be a pro-only feature, but as of Unity 5 it’s available for everyone.

2 Likes