C # bénéficierait-il des distinctions entre les types d’énumérateurs, comme les itérateurs C ++?

StackOverflow https://stackoverflow.com/questions/1634934

  •  06-07-2019
  •  | 
  •  

Question

J'ai réfléchi à la méthode IEnumerator.Reset(). J'ai lu dans la documentation MSDN que ce n'est là que pour l'interopérabilité COM. En tant que programmeur C ++, cela ressemble à un IEnumerator qui prend en charge Reset est ce que j'appellerais un avant itérateur , tandis qu'un PrintContents non pris en charge <=> est vraiment un itérateur en entrée .

La première partie de ma question est donc la suivante: cette compréhension est-elle correcte?

La deuxième partie de ma question est la suivante: serait-il avantageux en C # d’établir une distinction entre les itérateurs d’entrée et les itérateurs directs (ou & "énumérateurs &" si vous préférez)? Cela ne contribuerait-il pas à éliminer une certaine confusion parmi les programmeurs, comme celui que l'on trouve dans cette question SO sur le clonage d'itérateurs ?

EDIT: Clarification sur les itérateurs avant et entrée. Un itérateur en entrée garantit uniquement que vous pouvez énumérer les membres d'une collection (ou à partir d'une fonction de générateur ou d'un flux d'entrée) une seule fois. C’est exactement comment IEnumerator fonctionne en C #. Si vous pouvez ou non énumérer une seconde fois, cela dépend de la prise en charge de <=> ou non. Un itérateur avant n'a pas cette restriction. Vous pouvez énumérer les membres aussi souvent que vous le souhaitez.

Certains programmeurs C # ne sous-estiment pas pourquoi un <=> ne peut pas être utilisé de manière fiable dans un algorithme multipass. Considérez le cas suivant:

void PrintContents(IEnumerator<int> xs)
{
  while (iter.MoveNext())
    Console.WriteLine(iter.Current); 
  iter.Reset();
  while (iter.MoveNext())
    Console.WriteLine(iter.Current); 
}

Si nous appelons <=> dans ce contexte, aucun problème:

List<int> ys = new List<int>() { 1, 2, 3 }
PrintContents(ys.GetEnumerator()); 

Cependant, observez les éléments suivants:

IEnumerable<int> GenerateInts() {   
  System.Random rnd = new System.Random();
  for (int i=0; i < 10; ++i)
    yield return Rnd.Next();
}

PrintContents(GenerateInts());

Si le <=> pris en charge <=>, c'est-à-dire les algorithmes multi-passes pris en charge, chaque fois que vous parcouriez la collection, il serait différent. Ce ne serait pas souhaitable, car ce serait un comportement surprenant. Cet exemple est un peu faux, mais il se produit dans le monde réel (par exemple, en lisant des flux de fichiers).

Était-ce utile?

La solution

Question intéressante. Mon avis est que, bien sûr, C # bénéficierait d'un bénéfice . Cependant, il ne serait pas facile d'ajouter.

La distinction existe en C ++ en raison de son système de type beaucoup plus flexible. En C #, vous ne disposez pas d'un moyen générique robuste pour cloner des objets, ce qui est nécessaire pour représenter des itérateurs en aval (pour prendre en charge l'itération en plusieurs passes). Et bien sûr, pour que cela soit vraiment utile, vous devrez également prendre en charge les itérateurs / énumérateurs bidirectionnels et à accès aléatoire. Et pour que tout fonctionne correctement, vous avez vraiment besoin d’une certaine forme de frappe de canard, comme le font les modèles C ++.

En fin de compte, les domaines d'application des deux concepts sont différents.

En C ++, les itérateurs sont supposés représenter tout ce que vous devez savoir sur une plage de valeurs. Étant donné deux itérateurs, je n'ai pas besoin du conteneur d'origine. Je peux trier, je peux chercher, je peux manipuler et copier des éléments autant que je veux. Le conteneur d'origine n'est pas dans l'image.

En C #, les enquêteurs ne sont pas censés en faire autant. En fin de compte, ils sont simplement conçus pour vous permettre de parcourir la séquence de manière linéaire.

En ce qui concerne Reset(), il est largement admis que ce fut une erreur de l’ajouter au départ. Si cela avait fonctionné et avait été mis en œuvre correctement, alors oui, vous pourriez dire que votre énumérateur était analogue aux itérateurs, mais en général, il vaut mieux l'ignorer comme une erreur. Et puis tous les énumérateurs ne ressemblent qu’aux itérateurs.

Malheureusement.

Autres conseils

Reset était une grosse erreur. J'appelle des manigances sur IEnumerable<T>. A mon avis, la manière correcte de refléter la distinction que vous faites entre & "Forward iterators &"; et " entrées d'itérateurs " dans le système de types .NET, on distingue IEnumerator<T> et <=>.

Voir aussi ceci réponse , où Eric Lippert de Microsoft (dans une capactiy officieuse, sans aucun doute, ce que je veux dire, c’est juste que c’est quelqu'un qui a plus de références que je n’ai à prétendre que c’était une erreur de conception) fait la même remarque dans ses commentaires. Voir aussi son superbe blog .

En venant de la perspective C #:

Vous n’utilisez presque jamais IEnumerator directement. Habituellement, vous faites une instruction foreach qui attend un IEnumerable.

IEnumerable _myCollection;
...
foreach (var item in _myCollection) { /* Do something */ }

Vous ne faites pas circuler Reset() non plus. Si vous souhaitez transmettre une collection nécessitant une itération, transmettez-nous <=>. Étant donné que <=> a une seule fonction, qui renvoie un <=>, il peut être utilisé pour itérer la collection plusieurs fois (plusieurs passes).

Il n’est pas nécessaire d’utiliser une <=> fonction sur <=> car si vous souhaitez recommencer, il vous suffit de jeter l’ancienne (ordures ramassées) et d’en obtenir une nouvelle.

Le framework .NET serait extrêmement utile s’il était possible de demander à IEnumerator<T> quelles capacités il pouvait prendre en charge et quelles promesses il pouvait faire. De telles fonctionnalités seraient également utiles dans IEnumerable<T>, mais le fait de pouvoir poser les questions d’un énumérateur permettrait au code pouvant recevoir un énumérateur d’encapsuleurs tel que ReadOnlyCollection d’utiliser la collection sous-jacente de manière améliorée sans avoir à impliquer l’encapsuleur.

Étant donné qu'un énumérateur d'une collection pouvant être énuméré dans son intégralité et qui n'est pas trop volumineux, on pourrait en tirer un ReadOnlyCollection<T> qui produirait toujours la même séquence d'éléments (en particulier l'ensemble des éléments restants). dans l'énumérateur) en lisant l'intégralité de son contenu dans un tableau, en supprimant et en supprimant l'énumérateur, et en obtenant un énumérateur du tableau (en utilisant celui-ci à la place de l'énumérateur abandonné d'origine), en enroulant le tableau dans un <=>, puis en renvoyant ce dernier. . Bien qu'une telle approche fonctionnerait avec n'importe quel type de collection énumérable répondant aux critères ci-dessus, elle serait terriblement inefficace pour la plupart d'entre elles. Disposer d’un moyen de demander à un enquêteur de restituer le contenu restant dans un <=> immuable, permettrait à de nombreux types d’énumérateurs de réaliser l’action indiquée plus efficacement.

Je ne pense pas. J'appellerais IEnumerable un itérateur direct et un itérateur d'entrée. Cela ne vous permet pas de revenir en arrière ni de modifier la collection sous-jacente. Avec l'ajout du mot clé foreach, la plupart du temps, les itérateurs sont pratiquement absents.

Opinion: La différence entre les itérateurs d’entrée ( obtenez chacun ) et les itérateurs de sortie ( fait quelque chose pour chacun ) est trop triviale pour justifier un ajout au framework. De plus, pour créer un itérateur en sortie, vous devez transmettre un délégué à l’itérateur. L'itérateur d'entrée semble plus naturel aux programmeurs C #.

Il y a aussi IList<T> si le programmeur veut un accès aléatoire.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top