使用的foreach当内存分配循环在C# [英] Memory allocation when using foreach loops in C#

查看:663
本文介绍了使用的foreach当内存分配循环在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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆