Question

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

Alors pourquoi, dans les collections génériques de la BCL, sont tous les agents recenseurs mutables struct? Certes, il devait y avoir une bonne raison. La seule chose qui me vient est que struct peut être copié facilement, préservant ainsi l'état recenseur à un point arbitraire. Mais l'ajout d'une méthode Copy() à l'interface IEnumerator aurait été moins gênant, donc je ne vois pas cela comme étant une justification logique lui-même.

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.

Était-ce utile?

La solution

En effet, il est pour des raisons de performance. L'équipe de BCL a fait un beaucoup de la recherche sur ce point avant de décider d'aller avec ce que vous appelez à juste titre comme une pratique suspecte et dangereuse. L'utilisation d'un type de valeur mutable

Vous demandez pourquoi cela ne cause pas la boxe. Il est parce que le compilateur C # ne génère pas de code pour farcir boîte à IEnumerable ou IEnumerator dans une boucle foreach si elle peut l'éviter!

Quand on voit

foreach(X x in c)

la première chose que nous faisons est de vérifier pour voir si c est une méthode appelée GetEnumerator. Le cas échéant, nous vérifions pour voir si le type retourne a la méthode MoveNext et le courant de la propriété. Si elle le fait, la boucle foreach est entièrement réalisé à l'aide des appels directs à ces méthodes et propriétés. Seulement si « le modèle » ne peut pas être adapté ne nous retombons à la recherche des interfaces.

Cela a deux effets souhaitables.

Tout d'abord, si la collection est, par exemple, une collection de ints, mais il a été écrit avant que les types génériques ont été inventés, il ne prend pas la peine de boxe de la boxe la valeur du courant à l'objet et unboxing ensuite à int. Si le courant est une propriété qui retourne un entier, nous utilisons simplement.

En second lieu, si le recenseur est un type de valeur, alors il ne boîte pas le recenseur IEnumerator.

Comme je l'ai dit, l'équipe de BCL a fait beaucoup de recherches sur ce sujet et a découvert que la grande majorité du temps, la peine d'allouer et désaffecter le recenseur était assez grande qu'il faisait la peine un type de valeur, même si cela peut causer quelques bugs fous.

Par exemple, considérez ceci:

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

Vous pourriez vous attendre à juste titre la tentative de muter h à l'échec, et en effet il fait. Le compilateur détecte que vous essayez de changer la valeur de quelque chose qui a une disposition dans l'attente, et que cela pourrait provoquer l'objet qui doit être disposé à ne pas être réellement disposé.

Supposons maintenant que vous avez eu:

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

Qu'est-ce qui se passe ici? Vous pourriez vous attendre raisonnablement que le compilateur ferait ce qu'il fait si h était un champ en lecture seule:

Autres conseils

méthodes sont inline lorsque Struct type de struct est connu au moment de la compilation et la méthode d'appel via l'interface est lente, donc la réponse est:. En raison de la raison de la performance

scroll top