Je viens de lire un article intéressant intitulé Trop mignon avec le rendement c #
Cela m'a fait me demander quelle est la meilleure façon de détecter si un IEnumerable est une collection énumérable réelle, ou s'il s'agit d'une machine d'état générée avec le mot-clé yield.
Par exemple, vous pouvez modifier DoubleXValue (de l'article) en quelque chose comme:
private void DoubleXValue(IEnumerable<Point> points)
{
if(points is List<Point>)
foreach (var point in points)
point.X *= 2;
else throw YouCantDoThatException();
}
Question 1) Existe-t-il une meilleure façon de procéder?
Question 2) Est-ce quelque chose dont je dois m'inquiéter lors de la création d'une API?
c#
api-design
ConditionRacer
la source
la source
ICollection<T>
serait un meilleur choix car toutes les collections ne le sont pasList<T>
. Par exemple, les tableauxPoint[]
implémententIList<T>
mais ne le sont pasList<T>
.IEnumerable
est immuable dans le sens où vous ne pouvez pas modifier une collection (ajouter / supprimer) lors de son énumération, mais si les objets de la liste sont modifiables, il est parfaitement raisonnable de les modifier lors de l'énumération de la liste.ToList
? msdn.microsoft.com/en-us/library/bb342261.aspx Il ne détecte pas s'il a été généré par yield, mais à la place, il le rend inutile.Réponses:
Si je comprends bien, votre question semble reposer sur une prémisse incorrecte. Voyons si je peux reconstruire le raisonnement:
Le problème est que la deuxième prémisse est fausse. Même si vous pouviez détecter si un IEnumerable donné était le résultat d'une transformation de bloc d'itérateur (et oui, il existe des moyens de le faire), cela ne serait pas utile car l'hypothèse est fausse. Illustrons pourquoi.
D'accord, nous avons quatre méthodes. S1 et S2 sont des séquences générées automatiquement; S3 et S4 sont des séquences générées manuellement. Supposons maintenant que nous ayons:
Le résultat pour S1 et S4 sera 0; chaque fois que vous énumérez la séquence, vous obtenez une nouvelle référence à un M créé. Le résultat pour S2 et S3 sera 100; chaque fois que vous énumérez la séquence, vous obtenez la même référence à M que vous avez obtenue la dernière fois. Que le code de séquence soit généré automatiquement ou non est orthogonal à la question de savoir si les objets énumérés ont une identité référentielle ou non. Ces deux propriétés - génération automatique et identité référentielle - n'ont en fait rien à voir l'une avec l'autre. L'article auquel vous avez lié les confond un peu.
À moins qu'un fournisseur de séquence ne soit documenté comme offrant toujours des objets ayant une identité référentielle , il est imprudent de supposer qu'il le fait.
la source
Je pense que le concept clé est que vous devez traiter toute
IEnumerable<T>
collection comme immuable : vous ne devez pas en modifier les objets, vous devez créer une nouvelle collection avec de nouveaux objets:(Ou écrivez le même plus succinctement en utilisant LINQ
Select()
.)Si vous voulez vraiment modifier les éléments de la collection, ne pas utiliser
IEnumerable<T>
, l' utilisationIList<T>
(ou peut - êtreIReadOnlyList<T>
si vous ne voulez pas modifier la collection elle - même, seuls les éléments qu'il contient , et vous êtes sur .Net 4.5):De cette façon, si quelqu'un tente d'utiliser votre méthode avec
IEnumerable<T>
, elle échouera au moment de la compilation.la source
IEnumerable
fois. La nécessité de le faire plus d'une fois peut être évitée en appelantToList()
et en itérant sur la liste, bien que dans le cas spécifique indiqué par l'OP, je préfère la solution de svick consistant à faire en sorte que DoubleXValue renvoie également un IEnumerable. Bien sûr, dans du vrai code, j'utiliserais probablementLINQ
pour générer ce type deIEnumerable
. Cependant, le choix de svick estyield
logique dans le contexte de la question du PO.Select()
. Et en ce qui concerne plusieurs itérations deIEnumerable
: ReSharper est utile, car il peut vous avertir lorsque vous faites cela.Personnellement, je pense que le problème mis en évidence dans cet article provient de la surutilisation de l'
IEnumerable
interface par de nombreux développeurs C # ces jours-ci. Il semble être devenu le type par défaut lorsque vous avez besoin d'une collection d'objets - mais il y a beaucoup d'informations quiIEnumerable
ne vous disent tout simplement pas ... de manière cruciale: si oui ou non il a été réifié *.Si vous trouvez que vous devez détecter si un
IEnumerable
est vraiment unIEnumerable
ou quelque chose d'autre, l'abstraction a fui et vous êtes probablement dans une situation où votre type de paramètre est un peu trop lâche. Si vous avez besoin d'informations supplémentaires quiIEnumerable
seules ne fournissent pas, expliquez cette exigence en choisissant l'une des autres interfaces telles queICollection
ouIList
.Modifier pour les downvoters Veuillez revenir en arrière et lire attentivement la question. L'OP demande un moyen de s'assurer que les
IEnumerable
instances ont été matérialisées avant d'être transmises à sa méthode API. Ma réponse répond à cette question. Il y a un problème plus large concernant la bonne façon d'utiliserIEnumerable
, et certainement les attentes du code que l'OP a collé sont basées sur une mauvaise compréhension de ce problème plus large, mais l'OP n'a pas demandé comment utiliser correctementIEnumerable
, ou comment travailler avec des types immuables . Ce n'est pas juste de me déprécier de ne pas avoir répondu à une question qui n'a pas été posée.* J'utilise le terme
reify
, d'autres utilisentstabilize
oumaterialize
. Je ne pense pas qu'une convention commune ait été adoptée par la communauté pour l'instant.la source
ICollection
etIList
est qu'ils sont mutables (ou du moins ressemblent à ça).IReadOnlyCollection
et àIReadOnlyList
partir de .Net 4.5 résoudre certains de ces problèmes.