Using foreach still bad in 4.6 ?

I’ve read many topics about why not using foreach but still I noticed that the dates vary from many years ago to the end of 2013. So my question is: How bad it is to use foreach in 4.6? Same as previous versions of unity or better? And if not better really what does “bad” mean cause currently I’m using quite alot foreach loops in my game and still don’t feel much of a inconvinience or at least I don’t see it. If not so bad for what purposes can I use it and when? Also if use some how can I clean the mess left behind?

foreach is NOT bad. It’s not about the foreach itself, it’s about the Enumerator object that gets created, more importantly how it’s implemented. A foreach is just a wrapper.

foreach(var item in list)
   do(item);

is equivalent to:

var iter = list.GetEnumerator();
try
{
    while(iter.MoveNext())
    {
       var item = iter.Current;
       do(item);
    }
}
finally
{
    iter.Dispose();
}

So it’s about what does GetEnumerator do. You can write enumerators that doesn’t allocate thus no garbage thus using a foreach is safe. Give context and you’ll get better help.

Remember, be correct first, then fast. Optimize for readability and ease of use first, profile, ONLY THEN optimize for performance. Don’t waste your time trying to optimize performance when it isn’t a problem to begin with. Profile, profile, and profile.

[EDIT] Here’s an example showing a custom list enumerator that doesn’t allocate:

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

public class Test : MonoBehaviour
{
    ProfilerBlock profiler;
    List<int> list;

    void Awake()
    {
        list = new List<int>() { 1, 2, 3, 4, 5 };
        profiler = new ProfilerBlock();
    }

    void Update()
    {
        using (profiler.Begin("Custom.Iter")) // 0 bytes
        {
            foreach (var x in list.Iter())
            {
                int y = x;
            }
        }

        using (profiler.Begin("List.Iter")) // 40 bytes in my machine, 64 bit editor
        {
            foreach (var x in list)
            {
                int y = x;
            }
        }
    }
}

public class ProfilerBlock : IDisposable
{
    public IDisposable Begin(string sample)
    {
        Profiler.BeginSample(sample);
        return this;
    }

    public void Dispose()
    {
        Profiler.EndSample();
    }
}

public static class ListExt
{
    public static GCFreeListIterator<T> Iter<T>(this List<T> list)
    {
        return GCFreeListIterator<T>.Iter(list);
    }
}

public class GCFreeListIterator<T> : IDisposable
{
    private int index;
    private List<T> list;

    static readonly Stack<GCFreeListIterator<T>> pool = new Stack<GCFreeListIterator<T>>(4);

    private GCFreeListIterator() { }

    public static GCFreeListIterator<T> Iter(List<T> list)
    {
        GCFreeListIterator<T> result;
        if (pool.Count == 0)
            result = new GCFreeListIterator<T>();
        else
            result = pool.Pop();

        result.list = list;
        result.index = -1;
        return result;
    }

    public GCFreeListIterator<T> GetEnumerator()
    {
        return this;
    }

    public T Current
    {
        get { return list[index]; }
    }

    public bool MoveNext()
    {
        return ++index < list.Count;
    }

    public void Dispose()
    {
        index = -1;
        pool.Push(this);
    }
}

WARNING:
This does NOT do any version checking on your list, so you are free to modify it while enumerating (change element value, add and remove elements). Make sure you know what you’re doing if you decide to do so. e.g.

[ContextMenu("Modify Test")]
void BeCareful()
{
    var nums = new List<int>() { 1, 2, 3, 4, 5 };
    foreach (var n in nums.Iter())
    {
        nums[0] = Random.Range(0, 5);
        if (nums[0] < 2)
            nums.Add(100);
        else
            nums.RemoveAt(nums.Count - 1);
    }

    nums.ForEach(x => print(x));
}

50585-vslider.png