64 votes

Pourquoi les collections BCL utilisent-elles des énumérateurs de structure et non des classes?

Nous savons tous mutable structures sont mal en général. Je suis aussi assez sûr que parce que IEnumerable<T>.GetEnumerator() renvoie type IEnumerator<T>, les structures sont immédiatement en boîte dans un type de référence, le calcul des coûts plus que si elles étaient simplement des types de référence pour commencer.

Alors pourquoi, dans la BCL générique collections, sont tous des agents recenseurs mutable structures? Sûrement, il devait avoir une bonne raison. La seule chose qui me vient à l'esprit est que les structures peuvent être copiées facilement, ce qui permet de préserver l'agent recenseur état à un point arbitraire. Mais l'ajout d'un Copy() méthode de l' IEnumerator interface aurait été moins gênant, alors je ne vois pas cela comme étant une justification logique sur son propre.

Même si je ne suis pas d'accord avec une décision de conception, je voudrais être en mesure de comprendre le raisonnement derrière elle.

93voto

Eric Lippert Points 300275

En effet, c'est pour des raisons de performances. La BCL équipe fait beaucoup de recherche sur ce point avant de décider d'aller avec ce que vous appelez à juste titre comme un suspect et dangereux de la pratique: l'utilisation d'une mutable type de valeur.

Vous vous demandez pourquoi ce n'est pas une cause de la boxe. C'est parce que le compilateur C# ne génère pas de code de zone de trucs à IEnumerable ou IEnumerator dans une boucle foreach, si on peut l'éviter!

Lorsque nous voir

foreach(X x in c)

la première chose à faire est de vérifier pour voir si c est une méthode GetEnumerator. Si c'est le cas, nous allons vérifier si le type il retourne a la méthode MoveNext et de la propriété actuelle. Si c'est le cas, la boucle foreach est produite entièrement à l'aide de diriger les appels à ces méthodes et propriétés. Seulement si "le modèle" ne peut être égalée ne nous retombons à la recherche pour les interfaces.

Cela a deux effets désirables.

Tout d'abord, si la collection est, disons, une collection d'entiers, mais a été écrit avant que les types génériques ont été inventés, alors il ne prend pas la peine de boxe de boxe de la valeur du Courant à l'objet, et puis unboxing à int. Si le Courant est une propriété qui retourne un int, nous viens de l'utiliser.

Deuxièmement, si l'agent recenseur est un type valeur, alors il n'a pas de boîte de l'agent recenseur à IEnumerator.

Comme je l'ai dit, la BCL équipe a fait beaucoup de recherches sur cette question et a découvert que la grande majorité du temps, la peine de l'allocation et la désallocation de l'agent recenseur était assez grande qu'il valait la peine de faire un type de valeur, même si cela peut causer quelques fous de bugs.

Par exemple, considérez ceci:

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

Vous serait tout à fait en droit d'attendre de la tentative de muter h à l'échec, et en effet il ne. Le compilateur détecte que vous essayez de modifier la valeur de quelque chose qui a une cession, et que cela pourrait causer à l'objet qui doit être disposé à fait ne pas être éliminés.

Maintenant, supposons que vous avez eu:

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

Ce qui se passe ici? Vous pourriez raisonnablement s'attendre à ce que le compilateur pourrait faire ce qu'il fait si h était un champ en lecture seule: faire une copie, et la mutation de l'exemplaire afin de s'assurer que la méthode ne pas jeter des choses dans la valeur qui doit être éliminé.

Cependant, qui est en conflit avec notre intuition sur ce qui devrait se passer ici:

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

Nous nous attendons à ce que fait un MoveNext à l'intérieur d'un bloc using va déplacer l'agent recenseur à la suivante, qu'il s'agisse d'une structure ou d'un type de référence.

Malheureusement, le compilateur C# dispose aujourd'hui d'un bug. Si vous êtes dans cette situation, nous choisissons la stratégie à suivre de façon incohérente. Le comportement d'aujourd'hui est:

  • si le type de valeur de la variable d'être muté par l'intermédiaire d'une méthode est un local normal puis il est muté normalement

  • mais si c'est un hissé local (parce que c'est un fermé-sur une variable d'une fonction anonyme ou dans un itérateur bloc), puis le local est en fait générée comme un champ en lecture seule, et l'équipement qui assure que les mutations se produisent sur une copie qui prend le dessus.

Malheureusement, la spécification fournit peu d'indications sur cette question. Clairement, quelque chose est brisé parce que nous le faisons de façon incohérente, mais ce que le droit chose à faire n'est pas clair du tout.

6voto

STO Points 4597

Les méthodes de structure sont en ligne lorsque le type de structure est connu au moment de la compilation et que la méthode d'appel via l'interface est lente. La réponse est donc: pour des raisons de performance.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X