使用的foreach当内存分配循环在C# [英] Memory allocation when using foreach loops in C#
问题描述
我知道如何foreach循环在C#中工作的基本知识(如何做的foreach循环在C#中)
I know the basics on how foreach loops work in C# (How do foreach loops work in C#)
我想知道是否使用的foreach分配内存,可能会导致垃圾回收? (所有内置系统类型)。
I am wondering whether using foreach allocates memory that may cause garbage collections? (for all built in System types).
例如,使用反射对 System.Collections.Generic.List< T>
类,这里的GetEnumerator的实现:
For example, using Reflector on the System.Collections.Generic.List<T>
class, here's the implementation of GetEnumerator:
public Enumerator<T> GetEnumerator()
{
return new Enumerator<T>((List<T>) this);
}
在每次使用这个分配一个新的枚举(多垃圾)。
On every usage this allocates a new Enumerator (and more garbage).
做各类做到这一点?如果是这样,为什么呢? (可以在一个枚举被重用?)
Do all types do this? If so, why? (can't a single Enumerator be reused?)
推荐答案
的Foreach会导致分配,但至少在新版本的.NET和Mono,这并不重要,如果你处理的具体 System.Collections.Generic
类型或数组。旧版本的这些编译器(如单的使用Unity3D作为4.6.0f1版本)始终生成分配。
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 as of 4.6.0f1) always generate allocations.
C#编译器采用鸭打字寻找一个的GetEnumerator()
方法和用途,如果可能的。大多数的GetEnumerator()
在 System.Collection.Generic
类型的方法有返回结构的GetEnumerator()方法和阵列特殊处理。如果你的的GetEnumerator()
方法不分配,通常可以避开分配。
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.
不过,你总是会得到分配,如果你正在处理的接口之一的IEnumerable
,的IEnumerable&LT; T&GT;
,的IList
或的IList&LT; T&GT;
。即使你的实现类返回一个结构,该结构将被装箱,并转换为的IEnumerator
或的IEnumerator&LT; T&GT;
,这就需要有一个分配
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.
有第二个分配这是一个更复杂一点了解。借此foreach循环:
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
}
在此之前的C#5.0,它扩展为这样的事情(与某些小的差异):
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();
}
在名单,其中,INT&GT; .Enumerator
是一个结构,并不需要被分配在堆上,投枚举的系统.IDisposable
框的结构,这是一个分配。该规范改变与C#5.0,禁止配置,但是.NET <一href="http://blogs.msdn.com/b/ericlippert/archive/2011/03/14/to-box-or-not-to-box-that-is-the-question.aspx">broke规范和优化配置走前面。
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.
这些分配是非常小的。需要注意的是分配是内存泄漏有很大不同,与垃圾收集,通常不必担心。不过,也有当你在乎甚至这些分配一些场景。我做的Unity3D的工作,我们不能有这种情况发生的每一场比赛帧操作的任何拨款,因为如果垃圾收集器运行时,你就会得到一个明显的困境。
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 we can't have any allocations in operations that happen every game frame because if the garbage collector runs, you get a noticeable lurch.
请注意在阵列foreach循环的特殊处理,不必调用Dispose。所以,据我所知,已经的foreach循环结束阵列时从未分配。
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.
有关参考,这里有一些测试功能(NOOP只是需要一个int和什么都不做):
For reference, here are some test functions (NoOp just takes an int and does nothing):
// doesn't allocate (in the foreach loop)
public static void TestHash()
{
HashSet<int> foo = new HashSet<int> { 1 };
foreach (int bar in foo) {
NoOp(bar);
}
}
// allocates
public static void TestIList()
{
IList<int> foo = new List<int> { 1 };
foreach (int bar in foo) {
NoOp(bar);
}
}
// doesn't allocate; even more optimized
public static void TestArray()
{
int[] foo = new int[] { 1 };
foreach (int bar in foo) {
NoOp(bar);
}
}
下面是反汇编查看,在编译在Visual Studio 2013例preSS,以优化打开后,同样的功能。即使没有优化,分配功能是相同的,但优化摆脱一些糠。
Here are the same functions viewed in ildasm, after being compiled in Visual Studio 2013 Express, with optimizations turned on. Even without optimizations, the allocations are the same, but the optimizations get rid of some chaff.
.method public hidebysig static void TestHash() cil managed
{
// Code size 65 (0x41)
.maxstack 2
.locals init ([0] class [System.Core]System.Collections.Generic.HashSet`1<int32> foo,
[1] int32 bar,
[2] class [System.Core]System.Collections.Generic.HashSet`1<int32> '<>g__initLocal3',
[3] valuetype [System.Core]System.Collections.Generic.HashSet`1/Enumerator<int32> CS$5$0000)
IL_0000: newobj instance void class [System.Core]System.Collections.Generic.HashSet`1<int32>::.ctor()
IL_0005: stloc.2
IL_0006: ldloc.2
IL_0007: ldc.i4.1
IL_0008: callvirt instance bool class [System.Core]System.Collections.Generic.HashSet`1<int32>::Add(!0)
IL_000d: pop
IL_000e: ldloc.2
IL_000f: stloc.0
IL_0010: ldloc.0
IL_0011: callvirt instance valuetype [System.Core]System.Collections.Generic.HashSet`1/Enumerator<!0> class [System.Core]System.Collections.Generic.HashSet`1<int32>::GetEnumerator()
IL_0016: stloc.3
.try
{
IL_0017: br.s IL_0027
IL_0019: ldloca.s CS$5$0000
IL_001b: call instance !0 valuetype [System.Core]System.Collections.Generic.HashSet`1/Enumerator<int32>::get_Current()
IL_0020: stloc.1
IL_0021: ldloc.1
IL_0022: call void Test::NoOp(int32)
IL_0027: ldloca.s CS$5$0000
IL_0029: call instance bool valuetype [System.Core]System.Collections.Generic.HashSet`1/Enumerator<int32>::MoveNext()
IL_002e: brtrue.s IL_0019
IL_0030: leave.s IL_0040
} // end .try
finally
{
IL_0032: ldloca.s CS$5$0000
IL_0034: constrained. valuetype [System.Core]System.Collections.Generic.HashSet`1/Enumerator<int32>
IL_003a: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_003f: endfinally
} // end handler
IL_0040: ret
} // end of method Test::TestHash
.method public hidebysig static void TestIList() cil managed
{
// Code size 58 (0x3a)
.maxstack 2
.locals init ([0] class [mscorlib]System.Collections.Generic.IList`1<int32> foo,
[1] int32 bar,
[2] class [mscorlib]System.Collections.Generic.List`1<int32> '<>g__initLocal4',
[3] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> CS$5$0000)
IL_0000: newobj instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor()
IL_0005: stloc.2
IL_0006: ldloc.2
IL_0007: ldc.i4.1
IL_0008: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_000d: ldloc.2
IL_000e: stloc.0
IL_000f: ldloc.0
IL_0010: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
IL_0015: stloc.3
.try
{
IL_0016: br.s IL_0025
IL_0018: ldloc.3
IL_0019: callvirt instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
IL_001e: stloc.1
IL_001f: ldloc.1
IL_0020: call void Test::NoOp(int32)
IL_0025: ldloc.3
IL_0026: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
IL_002b: brtrue.s IL_0018
IL_002d: leave.s IL_0039
} // end .try
finally
{
IL_002f: ldloc.3
IL_0030: brfalse.s IL_0038
IL_0032: ldloc.3
IL_0033: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0038: endfinally
} // end handler
IL_0039: ret
} // end of method Test::TestIList
.method public hidebysig static void TestArray() cil managed
{
// Code size 45 (0x2d)
.maxstack 3
.locals init ([0] int32[] foo,
[1] int32 bar,
[2] int32[] CS$0$0000,
[3] int32[] CS$6$0001,
[4] int32 CS$7$0002)
IL_0000: ldc.i4.1
IL_0001: newarr [mscorlib]System.Int32
IL_0006: stloc.2
IL_0007: ldloc.2
IL_0008: ldc.i4.0
IL_0009: ldc.i4.1
IL_000a: stelem.i4
IL_000b: ldloc.2
IL_000c: stloc.0
IL_000d: ldloc.0
IL_000e: stloc.3
IL_000f: ldc.i4.0
IL_0010: stloc.s CS$7$0002
IL_0012: br.s IL_0025
IL_0014: ldloc.3
IL_0015: ldloc.s CS$7$0002
IL_0017: ldelem.i4
IL_0018: stloc.1
IL_0019: ldloc.1
IL_001a: call void Test::NoOp(int32)
IL_001f: ldloc.s CS$7$0002
IL_0021: ldc.i4.1
IL_0022: add
IL_0023: stloc.s CS$7$0002
IL_0025: ldloc.s CS$7$0002
IL_0027: ldloc.3
IL_0028: ldlen
IL_0029: conv.i4
IL_002a: blt.s IL_0014
IL_002c: ret
} // end of method Test::TestArray
这篇关于使用的foreach当内存分配循环在C#的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!