是否列出<;T>;在foreach中的C#中创建垃圾
如果我错了,请纠正我,但在执行foreach时,是否列出<;T>;在foreach中的C#中创建垃圾,c#,performance,generics,garbage-collection,C#,Performance,Generics,Garbage Collection,如果我错了,请纠正我,但在执行foreach时,IEnumerable会创建垃圾,无论T是什么。但我想知道你是否有一个列表,其中T是实体。然后假设列表中有一个派生类,如Entity2D。它必须为每个派生类创建一个新的枚举数吗?所以制造垃圾 还有一个接口,比如说,IEntity作为创建垃圾的工具?List的GetEnumerator方法实际上是非常有效的 当您在列表的元素中循环时,它调用GetEnumerator。这反过来会生成一个内部结构,其中包含对原始列表的引用、索引和版本ID,以跟踪列表中的
IEnumerable
会创建垃圾,无论T是什么。但我想知道你是否有一个列表
,其中T是实体。然后假设列表中有一个派生类,如Entity2D。它必须为每个派生类创建一个新的枚举数吗?所以制造垃圾
还有一个接口,比如说,IEntity
作为创建垃圾的工具?List
的GetEnumerator方法实际上是非常有效的
当您在列表的元素中循环时,它调用GetEnumerator。这反过来会生成一个内部结构
,其中包含对原始列表的引用、索引和版本ID,以跟踪列表中的更改
然而,由于正在使用结构,它实际上并没有创建GC将要处理的“垃圾”
< >“为每个派生类创建新枚举器”.NET泛型与C++模板不同。在.NET中,列表
类(及其内部的枚举器
结构)定义一次,可用于任何T。使用时,需要该特定类型的T的泛型类型,但这只是新创建的类型的类型信息,通常非常小。这与C++模板不同,例如,每个类型都是在编译时创建的,并且“内置”到可执行文件。
在.NET中,可执行文件指定列表
,而不是列表
,列表
,等等的定义。无论它是列表
,列表
,还是列表
,GetEnumerator
,都将每个foreach调用一次。此外,例如List
是否包含Entity2D
的实例并不重要。IEnumerable
的GetEnumerator
实现可能会创建要收集的引用对象。正如Reed所指出的,MS.NET中的List
通过只使用值类型来避免这种情况。我想您可能会感兴趣,这解释了为什么List(T)不会创建“垃圾”,而不是Collection(T):
现在,棘手的部分来了。谣传System.Collections.Generic中的许多类型在使用foreach时不会分配枚举数。例如,List的GetEnumerator返回一个结构,它将位于堆栈上。如果您不相信我,请使用.NET Reflector查找您自己。为了向自己证明列表上的foreach不会导致任何堆分配,我将实体更改为列表,执行了完全相同的foreach循环,并运行了探查器。没有统计员
[……]
然而,以上肯定有一个警告。列表上的Foreach循环仍然会生成垃圾。[将列表强制转换为IEnumerable]即使我们仍在对列表执行foreach,但当列表强制转换为接口时,必须将值类型枚举数装箱并放置在堆上
一个有趣的注意事项:正如所示,该类型实际上是一个结构。这既是好的,也是可怕的
在某种意义上说,在列表上调用foreach
实际上不会创建垃圾,因为没有为垃圾收集器分配新的引用类型对象
从某种意义上说,GetEnumerator
的返回值突然变成了一种值类型,几乎违背了每个.NET开发人员的直觉,这是非常可怕的(通常预期GetEnumerator
将返回一个非描述的IEnumerator
,因为这是IEnumerable
合同所保证的;List
通过显式实现IEnumerable.GetEnumerator
并公开IEn的更具体实现来解决这个问题umerator
恰好是一种值类型)
因此,任何代码,例如,将List.Enumerator
传递给一个单独的方法,该方法反过来调用该Enumerator对象上的MoveNext
,都会面临无限循环的潜在问题。如下所示:
int CountListMembers<T>(List<T> list)
{
using (var e = list.GetEnumerator())
{
int count = 0;
while (IncrementEnumerator(e, ref count)) { }
return count;
}
}
bool IncrementEnumerator<T>(IEnumerator<T> enumerator, ref int count)
{
if (enumerator.MoveNext())
{
++count;
return true;
}
return false;
}
int countlist成员(列表)
{
使用(var e=list.GetEnumerator())
{
整数计数=0;
while(递增枚举数(e,ref count)){
返回计数;
}
}
布尔递增枚举数(IEnumerator枚举数,ref int count)
{
if(枚举数.MoveNext())
{
++计数;
返回true;
}
返回false;
}
上面的代码非常愚蠢;它只是一个简单的例子,其中List.GetEnumerator
的返回值可能导致高度不直观(并且可能是灾难性的)行为
但正如我所说的,它仍然有点好,因为它不会产生垃圾;类列表显式实现IEnumerator
,因此对类型为列表的变量调用GetEnumerator
将导致它返回一个列表。Enumerator
,它具有值类型语义,然而,对包含对列表的引用的IEnumerator
类型的变量调用它将导致它返回一个IEnumerator
类型的值,该类型的值将具有引用语义。你是什么意思?迭代IEnumerable不会产生“垃圾”。哈哈,你抽了什么?还定义了“垃圾”。foreach从一开始就为我提供了一个列表。@driis它创建了IEnumerator的新实例。这是可以考虑的garbage@Andrey:虽然列表生成的IEnumerator是一个结构,但它不会增加任何GC压力,因为它保存在堆栈上(当然,除非您自己将其粘贴到类中,但这会非常非常奇怪…@Reed:不会那么奇怪。例如,看看+1。它们也不同于Java的泛型,后者纯粹是一种编译时特性(所有出现的T
类型都是