Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

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

Discussion in 'Scripting' started by Jeremie-de-Vos, Jan 15, 2017.

  1. Jeremie-de-Vos

    Jeremie-de-Vos

    Joined:
    Jun 5, 2015
    Posts:
    120
    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!
     
  2. Deleted User

    Deleted User

    Guest

  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    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.
     
    phobos2077 and Kiwasi like this.
  4. Deleted User

    Deleted User

    Guest

    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:

    Code (CSharp):
    1. int amount = 10
    2. int start = 10
    3.  
    4. for(int i = 0; i < amount; i++)
    5. {
    6.     someGOArray[ start + i ].SetActive(true);
    7. }
    8.  
    9. // or maybe work backwards
    10.  
    11. for(amount; amount > 0; amount--)
    12. {
    13.     someGOArray[ start + amount ].SetActive(true);
    14. }
    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.
     
    Kiwasi likes this.
  5. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,965
    If I'm not mistaken doesn't foreach generate about 24 bytes of garbage? He may be referring to that.
     
  6. Deleted User

    Deleted User

    Guest

    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.
     
    Kiwasi likes this.
  7. Pengocat

    Pengocat

    Joined:
    Dec 7, 2016
    Posts:
    140
    The foreach garbage problem should be fixed in Unity 5.5
     
    Ryiah likes this.
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    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.
     
    phobos2077, Kiwasi and Ryiah like this.
  9. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,965
    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/
     
    Pengocat likes this.
  10. Deleted User

    Deleted User

    Guest

    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.
     
  11. Pengocat

    Pengocat

    Joined:
    Dec 7, 2016
    Posts:
    140
    I would guess by using the stack instead of the heap?
     
  12. Deleted User

    Deleted User

    Guest

  13. Deleted User

    Deleted User

    Guest

  14. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    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.

    Code (csharp):
    1.  
    2. var lst = new List<object>();
    3. //fill lst
    4. List<object>.Enumerator e = lst.GetEnumerator();
    5. while(e.MoveNext())
    6. {
    7.     var obj = e.Current;
    8. }
    9.  
    As you can see List<T>Enumerator is a struct:
    https://msdn.microsoft.com/en-us/library/x854yt9s(v=vs.110).aspx

    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<T>, but store it in a variable of type IList<T>, 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<T> 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).
     
    Last edited: Jan 17, 2017
    Novack, AndyGainey and Deleted User like this.
  15. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    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.
     
    Laperen likes this.
  16. Deleted User

    Deleted User

    Guest

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

    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. ;)

    Better: in this script below that I found here:
    how would you replace foreach by MoveNext ().

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class SomeClass : MonoBehaviour
    6. {
    7.     void Start ()
    8.     {
    9.         //This is how you create a list. Notice how the type
    10.         //is specified in the angle brackets (< >).
    11.         List<BadGuy> badguys = new List<BadGuy>();
    12.        
    13.         //Here you add 3 BadGuys to the List
    14.         badguys.Add( new BadGuy("Harvey", 50));
    15.         badguys.Add( new BadGuy("Magneto", 100));
    16.         badguys.Add( new BadGuy("Pip", 5));
    17.        
    18.         badguys.Sort();
    19.        
    20.         foreach(BadGuy guy in badguys)
    21.         {
    22.             print (guy.name + " " + guy.power);
    23.         }
    24.        
    25.         //This clears out the list so that it is
    26.         //empty.
    27.         badguys.Clear();
    28.     }
    29. }
    Thanks! :)
     
    Last edited by a moderator: Jan 17, 2017
  17. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    Fairly simple swap out:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. public class SomeClass : MonoBehaviour
    7. {
    8.     void Start ()
    9.     {
    10.         //This is how you create a list. Notice how the type
    11.         //is specified in the angle brackets (< >).
    12.         List<BadGuy> badguys = new List<BadGuy>();
    13.  
    14.         //Here you add 3 BadGuys to the List
    15.         badguys.Add( new BadGuy("Harvey", 50));
    16.         badguys.Add( new BadGuy("Magneto", 100));
    17.         badguys.Add( new BadGuy("Pip", 5));
    18.  
    19.         badguys.Sort();
    20.        
    21.         var e = badguys.GetEnumerator();
    22.         while(e.MoveNext())
    23.         {
    24.             BadGuy guy = e.Current;
    25.             print (guy.name + " " + guy.power);
    26.         }
    27.  
    28.         //This clears out the list so that it is
    29.         //empty.
    30.         badguys.Clear();
    31.     }
    32. }
    33.  
    Note though, since that List<BadGuy> is so short lived, it'll generate garbage.

    But yeah, that's the basic boilerplate for ya.
     
    Deleted User likes this.
  18. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    Quick test foreach over a List<int>:
    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.
     
    phobos2077, Glader, Ryiah and 3 others like this.
  19. Deleted User

    Deleted User

    Guest

    How do you do that kind of tests?
     
    phobos2077 likes this.
  20. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,514
    Profiler

    It's built into Unity

    https://docs.unity3d.com/Manual/Profiler.html

    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.
     
    Ryiah and Deleted User like this.