为什么首创置业集合使用结构调查员,而不是类? [英] Why do BCL Collections use struct enumerators, not classes?

查看:181
本文介绍了为什么首创置业集合使用结构调查员,而不是类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们都知道可变的结构是邪恶的一般来说。我也pretty的肯定,因为的IEnumerable< T> .GetEnumerator()返回类型为的IEnumerator< T> ,该结构被立即盒装成引用类型,耗资超过如果他们只是引用类型的开始。

We all know mutable structs are evil in general. I'm also pretty sure that because IEnumerable<T>.GetEnumerator() returns type IEnumerator<T>, the structs are immediately boxed into a reference type, costing more than if they were simply reference types to begin with.

那么,为什么,在BCL泛型集合,是所有调查员可变的结构?当然,还有不得不是一个很好的理由。这发生在我的唯一的事情是,结构很容易被复制,因此$ P $任意点pserving枚举状态。但添加复制()方法将的IEnumerator 的界面就会有更少的麻烦,所以我不认为这作为自己的逻辑理由。

So why, in the BCL generic collections, are all the enumerators mutable structs? Surely there had to have been a good reason. The only thing that occurs to me is that structs can be copied easily, thus preserving the enumerator state at an arbitrary point. But adding a Copy() method to the IEnumerator interface would have been less troublesome, so I don't see this as being a logical justification on its own.

即使我不设计决定不服的,我希望能够了解它背后的原因。

Even if I don't agree with a design decision, I would like to be able to understand the reasoning behind it.

推荐答案

事实上,这是出于性能的考虑。首创置业团队做了的很多的的决定去与你正确地叫出一个可疑和危险的做法之前,在这一点上的研究:利用一个可变的值类型

Indeed, it is for performance reasons. The BCL team did a lot of research on this point before deciding to go with what you rightly call out as a suspicious and dangerous practice: the use of a mutable value type.

您问为什么这不会导致装箱。这是因为C#编译器不产生code框东西的IEnumerable或IEnumerator的在foreach循环中,如果能避免它!

You ask why this doesn't cause boxing. It's because the C# compiler does not generate code to box stuff to IEnumerable or IEnumerator in a foreach loop if it can avoid it!

当我们看到

foreach(X x in c)

我们做的第一件事是检查是否C有一个名为GetEnumerator方法。如果确实如此,那么我们检查它返回的类型是否有方法的MoveNext和财产电流。如果确实如此,那么完全使用直接调用这些方法和属性的foreach循环产生。只有当不匹配的格局我们退回到寻找接口。

the first thing we do is check to see if c has a method called GetEnumerator. If it does, then we check to see whether the type it returns has method MoveNext and property current. If it does, then the foreach loop is generated entirely using direct calls to those methods and properties. Only if "the pattern" cannot be matched do we fall back to looking for the interfaces.

这有两个满意的效果。

首先,如果集合是说,整数的集合,但写的泛型类型被发明之前,那么它没有考虑拳击的当前价值为对象,然后拆箱它为int的装箱损失。如果当前是返回一个int的属性,我们只是使用它。

First, if the collection is, say, a collection of ints, but was written before generic types were invented, then it does not take the boxing penalty of boxing the value of Current to object and then unboxing it to int. If Current is a property that returns an int, we just use it.

第二,如果枚举是值类型,那么它不会框中的枚举的IEnumerator。

Second, if the enumerator is a value type then it does not box the enumerator to IEnumerator.

就像我说的,首创置业团队做了很多的研究就这个问题和发现的大多数时间,分配的和释放的处罚的枚举数足够大,这是值得的它是值类型,即使这样做可能会导致一些疯狂的错误。

Like I said, the BCL team did a lot of research on this and discovered that the vast majority of the time, the penalty of allocating and deallocating the enumerator was large enough that it was worth making it a value type, even though doing so can cause some crazy bugs.

例如,考虑一下:

struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
    h = somethingElse;
}

您会很有理由期待变异小时,失败的尝试,实际上它的作用。编译器检测到您正试图改变的东西,有一个听候调遣的值,这样做可能会导致需要被设置成其实并不是处置的对象。

You would quite rightly expect the attempt to mutate h to fail, and indeed it does. The compiler detects that you are trying to change the value of something that has a pending disposal, and that doing so might cause the object that needs to be disposed to actually not be disposed.

现在假设你有:

struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
    h.Mutate();
}

在这里有什么会发生?你很可能会想到,编译器会做它如果h是一个只读字段:的进行复印,并发生变异副本,以确保该方法不会扔掉的东西,在需要设置的值。

What happens here? You might reasonably expect that the compiler would do what it does if h were a readonly field: make a copy, and mutate the copy in order to ensure that the method does not throw away stuff in the value that needs to be disposed.

不过,这与我们的直觉应该是什么在这里发生冲突:

However, that conflicts with our intuition about what ought to happen here:

using (Enumerator enumtor = whatever)
{
    ...
    enumtor.MoveNext();
    ...
}

我们期望做一个一个的MoveNext使用块内的将会的移动枚举到下一个无论它是一个结构或REF类型。

We expect that doing a MoveNext inside a using block will move the enumerator to the next one regardless of whether it is a struct or a ref type.

不幸的是,今天的C#编译器有一个bug。如果你是在这种情况下,我们选择遵循不一致哪种策略。今天的行为是:

Unfortunately, the C# compiler today has a bug. If you are in this situation we choose which strategy to follow inconsistently. The behaviour today is:

  • 如果该值类型变量被通过突变方法,是一种正常的地方则通常是突变

  • if the value-typed variable being mutated via a method is a normal local then it is mutated normally

但如果它是一个悬挂本地(因为它是一个封闭在匿名函数的变量或迭代器块),那么当地的的作为只读字段实际产生的,并确保突变发生在副本齿轮接管。

but if it is a hoisted local (because it's a closed-over variable of an anonymous function or in an iterator block) then the local is actually generated as a read-only field, and the gear that ensures that mutations happen on a copy takes over.

不幸的是,规范提供了关于这个问题的指导很少。显然,东西坏了,因为我们正在做的不一致,但什么的右键的事情并不完全清楚。

Unfortunately the spec provides little guidance on this matter. Clearly something is broken because we're doing it inconsistently, but what the right thing to do is not at all clear.

这篇关于为什么首创置业集合使用结构调查员,而不是类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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