Memory allocation when using foreach loops in C#

前端 未结 4 443
暖寄归人
暖寄归人 2021-02-03 23:52

I know the basics on how foreach loops work in C# (How do foreach loops work in C#)

I am wondering whether using foreach allocates memory that may cause garbage collecti

相关标签:
4条回答
  • 2021-02-04 00:23

    No, enumerating a list doesn't cause garbage collections.

    The enumerator for the List<T> class doesn't allocate memory from the heap. It's a structure, not a class, so the constructor doesn't allocate an object, it just returns a value. The foreach code would keep that value on the stack, not on the heap.

    Enumerators for other collections may be classes though, which would allocate an object on the heap. You would need to check the type of the enumerator for each case to be certain.

    0 讨论(0)
  • 2021-02-04 00:31

    Because an enumerator keeps hold of the current item. It's like a cursor compared to databases. If multiple threads would access the same enumerator, you would lose control of the sequence. And you would have to reset it to the first item every time a foreach consults it.

    0 讨论(0)
  • 2021-02-04 00:32

    As mentioned in comments, this generally should not be an issue you need worry about, as that is the point of Garbage Collection. That said, my understanding is that yes, each foreach loop will generate a new Enumerator object, which will eventually be garbage collected. To see why, look at the documentation for the interface here. As you can see, there is a function call which requests the next object. The ability to do this implies the enumerator has state, and must know which one is next. As to why this is necessary, image you're causing an interaction between every permutation of collection Items:

    foreach(var outerItem in Items)
    {
       foreach(var innterItem in Items)
       {
          // do something
       }
    }
    

    Here you have two enumerators on the same collection at the same time. Clearly a shared location would not accomplish your goal.

    0 讨论(0)
  • 2021-02-04 00:36

    Foreach can cause allocations, but at least in newer versions .NET and Mono, it doesn't if you're dealing with the concrete System.Collections.Generic types or arrays. Older versions of these compilers (such as the version of Mono used by Unity3D until 5.5) always generate allocations.

    The C# compiler uses duck typing to look for a GetEnumerator() method and uses that if possible. Most GetEnumerator() methods on System.Collection.Generic types have GetEnumerator() methods that return structs, and arrays are handled specially. If your GetEnumerator() method doesn't allocate, you can usually avoid allocations.

    However, you will always get an allocation if you are dealing with one of the interfaces IEnumerable, IEnumerable<T>, IList or IList<T>. Even if your implementing class returns a struct, the struct will be boxed and cast to IEnumerator or IEnumerator<T>, which requires an allocation.


    NOTE: Since Unity 5.5 updated to C# 6, I know of no current compiler release that still has this second allocation.

    There's a second allocation that is a little more complicated to understand. Take this foreach loop:

    List<int> valueList = new List<int>() { 1, 2 };
    foreach (int value in valueList) {
        // do something with value
    }
    

    Up until C# 5.0, it expands to something like this (with certain small differences):

    List<int>.Enumerator enumerator = valueList.GetEnumerator();
    try {
        while (enumerator.MoveNext()) {
            int value = enumerator.Current;
            // do something with value
        }
    }
    finally {
        IDisposable disposable = enumerator as System.IDisposable;
        if (disposable != null) disposable.Dispose();
    }
    

    While List<int>.Enumerator is a struct, and doesn't need to be allocated on the heap, the cast enumerator as System.IDisposable boxes the struct, which is an allocation. The spec changed with C# 5.0, forbidding the allocation, but .NET broke the spec and optimized the allocation away earlier.

    These allocations are extremely minor. Note that an allocation is very different from a memory leak, and with the garbage collection, you generally don't have to worry about it. However, there are some scenarios when you do care about even these allocations. I do Unity3D work and until 5.5, we couldn't have any allocations in operations that happen every game frame because when the garbage collector runs, you get a noticeable lurch.

    Note that foreach loops on arrays are handled specially and don't have to call Dispose. So as far as I can tell, foreach has never allocated when looping over arrays.

    0 讨论(0)
提交回复
热议问题